1

ExecutorService를 사용하여 설명 할 수없는 동작을 관찰하고 있습니다. 메모리에 약 800 명의 프로필을로드하는 응용 프로그램이 있습니다. 아래를 고려하십시오명백하게 유휴 상태의 스레드가있는 Java - ExecutorService

ExecutorService es = Executors.newFixedThreadPool(8); // 8 cores machine 
Runnable enricherService = new Runnable() { 

    @Override 
    public void run() { 
      doEnrichment(conn, tmpCachePerson); 
    } 
}; 

// tmpCachePerson is a ConcurrentLinkedQueue<Person> 
while (tmpCachePerson.isEmpty() == false) { 
    es.execute(enricherService); 
} 

es.shutdown(); 

try { 
    while (!es.awaitTermination(24L, TimeUnit.HOURS)) { 
     System.out.println("Waiting for termination"); 
    } 
} catch (InterruptedException e) { 
    // TODO Auto-generated catch block 
    e.printStackTrace(); 
} 

이 조각은 1 개의 스레드 풀에 비해 매우 느립니다. 나는 코드에 println을 두었고 모든 스레드가 너무 자주 (~ 4s), 모든 스레드가 멈추는 것을 볼 수 있으며 최대 16 초 동안 그곳에 앉아서 다시 배치를 시작할 수 있습니다. 완료하려면 50 초가 걸립니다. 그때 아래의 구현을 시도 :

Runnable enricher2Thread = new Runnable() { 

      @Override 
      public void run() { 
       while (tmpCachePerson.isEmpty() == false) { 
        doEnrichment(conn, tmpCachePerson); 
       } 
      } 
     }; 

     Thread t = new Thread(enricher2Thread); 
     t.start(); 
     try { 
      t.join(); 
     } catch (InterruptedException e) { 
      // TODO Auto-generated catch block 
      e.printStackTrace(); 
     } 

반면에이 작품을 콘솔에 인쇄를 중단하지 않고, 3 초에 완료 결코, 매우 빠르고, 하나 개의 스레드를 사용합니다.

첫 번째 조각의 고정 풀을 캐시 된 풀로 바꾸면이 스레드는 800 개의 스레드를 생성 한 후 3 초 만에 완료됩니다. 고정 풀에 800 개의 스레드를 배치하면 속도가 같아집니다. 누구나 고정 된 풀이 너무 자주 멈추는 이유를 이해할 수 있으며, 왜 그것이 1 스레드보다 빠르지 않은지 이해합니다. 아래는 8 개의 스레드로 볼 수있는 내용입니다. thread-1을 보면, 5 초 동안 간단한 게터에서 일시 중지됩니다. 로그의 다른 부분에서는 전체 작업이 250ms 정도 걸립니다.

2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting 
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting executing SQL 
2016-11-15 15:54:04.212 - pool-1-thread-1 - Done executing SQL in 0 ms 
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting adding 
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting Getting Val 
2016-11-15 15:54:04.212 - pool-1-thread-1 - Done Getting Val in 0 ms 
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting Getting Root 
2016-11-15 15:54:04.212 - pool-1-thread-1 - Done Getting Root in 0 ms 
2016-11-15 15:54:04.212 - pool-1-thread-1 - Starting Getting Path 
2016-11-15 15:54:04.212 - pool-1-thread-1 - Done Getting Path in 0 ms 
2016-11-15 15:54:04.212 - pool-1-thread-6 - Starting 
2016-11-15 15:54:04.212 - pool-1-thread-8 - Starting <-------------- All threads stop 
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting executing SQL 
2016-11-15 15:54:09.533 - pool-1-thread-6 - Starting executing SQL 
2016-11-15 15:54:09.533 - pool-1-thread-8 - Done executing SQL in 0 ms 
2016-11-15 15:54:09.533 - pool-1-thread-1 - Starting Getting Full Path 
2016-11-15 15:54:09.533 - pool-1-thread-6 - Done executing SQL in 5320 ms 
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting adding 
2016-11-15 15:54:09.533 - pool-1-thread-6 - Starting adding 
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting Getting Val 
2016-11-15 15:54:09.533 - pool-1-thread-6 - Starting Getting Val 
2016-11-15 15:54:09.533 - pool-1-thread-8 - Done Getting Val in 0 ms 
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting Getting Root 
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done Getting Full Path in 5320 ms 
2016-11-15 15:54:09.533 - pool-1-thread-1 - Starting Adding Image 
2016-11-15 15:54:09.533 - pool-1-thread-8 - Done Getting Root in 0 ms 
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done Adding Image in 0 ms 
2016-11-15 15:54:09.533 - pool-1-thread-8 - Starting Getting Path 
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done adding in 5321 ms 
2016-11-15 15:54:09.533 - pool-1-thread-1 - Starting setting 
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done setting in 0 ms 
2016-11-15 15:54:09.533 - pool-1-thread-1 - Done in 5321 ms 

이 코드를 어떻게 향상시킬 수 있고 왜 멈추는 지 알기 원하십니까? 이

편집을 도움이된다면 나는 doEnrichment()의 코드를 게시 할 수 있습니다

private void doEnrichment(Connection conn, ConcurrentLinkedQueue<Person> tmpCachePerson) { 
     Person person = tmpCachePerson.poll(); 

     if (person != null) { 
      ImageCollection personImageCollection; 
      String query = "SELECT epi.value, i.path FROM Image i " 
        + "INNER JOIN EntityImageRelationship eir ON eir.id_image = i.id " 
        + "INNER JOIN EntityType et ON eir.id_entity_type = et.id " 
        + "INNER JOIN EntityPrimaryImage epi ON epi.type_to_entity_uid = eir.type_to_entity_uid " 
        + "WHERE et.id = ? AND eir.id_entity_id = ? ORDER BY i.id ASC"; 

      String tagQuery = "SELECT id, value FROM Tag t INNER JOIN EntityTagRelationship etr ON etr.id_tag = t.id WHERE etr.id_entity = ? AND etr.id_entity_type = ?"; 

      try (PreparedStatement stmnt = conn.prepareStatement(query); 
        PreparedStatement tagStmnt = conn.prepareStatement(tagQuery)) { 
       personImageCollection = getEntityImages(conn, stmnt, person.getId(), person.getMovieEntityType()); 
       person.setImageCollection(personImageCollection); 
       person.getImageCollection().setPrimaryImageIcon(); 

       Set<String> tags = getEntityTags(conn, tagStmnt, person.getId(), person.getMovieEntityType()).keySet(); 
       person.setTags(tags); 

       personCache.add(person); 
      } catch (Exception e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 
     } 
    } 
+1

와 노동자를 시작

더 나은 패턴은 노동자의 폴링을 넣어하는 것입니다 끝난? 그렇다면 선물을 사용하고 get (long timeout, TimeUnit unit)을 호출합니다. – joseph

+0

예, 좋습니다. 코드가 제작에 들어갈 때까지이 작업을 수행합니다. – user292272

답변

1

이 원인이되지 않을 수도 있습니다,하지만 당신은 여기에 경쟁 조건이 있습니다 : 여기있다

while (tmpCachePerson.isEmpty() == false) { 
    es.execute(enricherService); 
} 

컨텍스트 스위칭이 발생한다고 절대적으로 보장 할 수는 없습니다. 그것이더라도 아마 예상보다 느릴 것입니다. 작업자가 시작하기 전에 루프가 백만 번 실행될 수 있습니다. 이 문제를 해결할 때까지는 더 이상 보지 마십시오. 그것은 많은 회전과 메모리 오버 헤드입니다.

Runnable enricherService = new Runnable() { 
    @Override 
    public void run() { 
     while (!tmpCachePerson.isEmpty()) { 
      doEnrichment(conn, tmpCachePerson); 
      // TODO: error handling? Should a failure in doEnrichment kill the worker? 
     } 
    } 
}; 

그리고 당신이 doEnrichment의보다 Runnable이시기를 결정하기 위해 종료 및 awaitTermination를 사용하고

for (int i = 0; i < 8; ++i) { 
    es.execute(enricherService); 
} 
+0

약 4.5 초 후에 실행됩니다. 하나의 스레드보다 1 초 이상 빠르지 만 내가 가진 것보다 낫습니다. 나는 해결책을 찾지 못했지만 더 가깝게 자리 잡았습니다. 감사합니다 – user292272

+0

당신은 데이터베이스를 쿼리하고 있습니다. 어떻게 연결 되니? 풀되지 않으면 성능 문제가 발생할 수 있습니다. 풀이 너무 작 으면 (<8) 스레드가 차단되어 연결을 기다립니다. 여러분의 연결에 대해'System.identityHashCode()'를 출력 해보십시오. 적어도 8 개 (800 개 미만)의 ID가 표시되는 것이 이상적입니다. 1 (또는 800) 만받는다면 풀을 사용해야합니다. ~ 4 점을 얻으면 더 큰 수영장이 필요합니다. –

+0

이것은 모든 스레드에 대해 단일 연결을 사용 중이거나 DB에 병목 현상이 있지만 연결 일 것 같은 냄새가납니다. –

관련 문제