Java 병렬 실행 중, 라인 안전에 주의하지 않으면 푹 빠질 수 있습니다

9973 단어 JavaCompletableFuture

개시하다


일 때문에 아래의 상담을 받았습니다.
"Compuletable Future를 사용하여 병렬 처리를 하면 수십 번에서 수백 번 정도가 한 번 정도 되므로 값을 제대로 되돌릴 수 없습니다."
나는 당시의 대응을 기록할 것이다.

문제.


운영 환경
  • JDK1.8 ( 8u181 )
  • docker 용기에서 실행 (alpine linux)
  • 네.
    다음과 같은 처리가 있다.
    Compuletable Future를 이용하여summary Item에 정보를 병렬로 저장합니다.xxxAPI.search(number); Item 정보를 얻기 위해 외부 API를 호출합니다.
    어디에 구린내가 잠복해 있습니까?
      public List<Item> search(int length) {
        //検索結果を保持する変数
        List<Item> summaryItem = new ArrayList<>(length);
    
        List<CompletableFuture<Void>> futures = new ArrayList<>(length);
        for (int i = 0; i < length; i++) {
          final int number = i;
          CompletableFuture<Void> f = CompletableFuture.runAsync(() -> {
            //外部APIを呼び出して、データの取得
            Item item = xxxAPI.search(number);
            summaryItem.add(item);
          }, pool);
          futures.add(f);
        }
    
        //全ての並列処理が終わるまで待機
        CompletableFuture<Void> all =
            CompletableFuture.allOf(futures.toArray(new CompletableFuture[length]));
        all.join();
    
        return summaryItem;
      }
    

    대답하다.


    다음 처리 부분입니다.
    ※ 실행환경과는 무관
            summaryItem.add(item);
    
    summaryItem은java입니다.util.ArayList입니다.ArryList는 스레드 보안이 아닙니다.
    JavaDoc 좀 보세요.
    이 실시는synchronized에 의해 이루어지지 않을 것이다.여러 스레드가 ArryList 인스턴스에 동시에 액세스하고 이 스레드 중 적어도 하나가 구조적으로 목록을 수정하는 경우 외부에서 동기화해야 합니다.구조적 수정은 하나 이상의 요소를 추가하거나 삭제하거나 기본 그룹의 크기를 현저하게 수정하는 처리를 말한다.요소 값만 설정하는 처리는 구조적인 변화가 아닙니다.이것은 보통 목록을 일부 대상에 자연스럽게 봉하여 실현하는 것이다.이러한 객체가 없는 경우 CollectionssynchronizedList 메소드 "랩"목록을 사용하십시오.목록 오류가synchronized에 접근하지 않는 것을 방지하기 위해 제작할 때 진행하는 것을 권장합니다.
    잘 써있네.

    대응


    단서를 사용하여 안전한 List.아니면, 원래는 단서로 안전하게 처리되었다.
    이런 식으로 대응하다.

    스레드 보안 목록 사용

    java.util.concurrent 포장에 완전 동기화 목록이 있습니다.이걸로 하자.
    List<Item> summaryItem = new ArrayList<>(length);
    
    상술한 코드는 아래와 같이 변경하기만 하면 정상적으로 운행할 수 있다.
    List<Item> summaryItem = new CopyOnWriteArrayList<>(length);
    
    상황에 따라 Collections.synchronizedList를 사용하면 해결할 수 있지만iterator를 사용하면 스스로 동기화해야 한다.

    스레드 보안 처리로 변경


    말하자면, 자바.util.function.Supplier 형식으로 값을 깎아주시면 됩니다.
    ※ 난 이게 좋아.summaryItem은 긴 범위 내에서 사용할 수 있기 때문이다.
      public List<Item> search(int length) {
        // 検索の並列実行処理
        List<CompletableFuture<Item>> futures = new ArrayList<>(length);
        for (int i = 0; i < length; i++) {
          final int number = i;
          CompletableFuture<Item> f = CompletableFuture.supplyAsync(() -> {
            //外部APIを呼び出して、データの取得
            return xxxAPI.search(number);
          }, pool);
          futures.add(f);
        }
    
        // 検索結果の収集
        try {
          List<Item> summaryItem = new ArrayList<>();
          for (CompletableFuture<Item> f : futures) {
            summaryItem.add(f.get());
          }
          return summaryItem;
        } catch (ExecutionException | InterruptedException ex) {
          throw new RuntimeException(ex);//適当に書いてます。
        }
      }
    
    Java API에 대한 어느 정도의 지식이 없으면 문제를 발견하기 어려울 정도로 매우 우연히 발생했다.
    테스트 코드도 대체로 통과했다.
    나는 이런 부분이 디버깅 등 코드 검사를 하는 것보다 더 유용하다고 생각한다.

    하고 싶은 일.


    CheckStyle에서 찾을 수 없을지도 모르지만, 나는 정적 코드로 시스템을 분석하는 도구로 문제를 발견할 수 있다고 생각한다.
    나는 앞으로 한번 찾아보고 싶다.

    좋은 웹페이지 즐겨찾기