2012-06-26 3 views
6

말은, 나는 데이터 객체를 가지고도스레드 풀, 공유 데이터, 자바 동기화

Collection<ValueRef> masterList = ...;

내가 : 각 데이터 객체가 마스터 콜렉션에 저장됩니다

class ValueRef { double value; }

각 작업에는 데이터 개체의 로컬 컬렉션이 있습니다 (각 데이터 개체는 masterList에 표시됨) :

,
class Job implements Runnable { 
    Collection<ValueRef> neededValues = ...; 
    void run() { 
     double sum = 0; 
     for (ValueRef x: neededValues) sum += x; 
     System.out.println(sum); 
    } 
} 

사용의 경우는 :

  1. for (ValueRef x: masterList) { x.value = Math.random(); }

  2. 일부 작업과 작업 큐를 채 웁니다.

  3. 일어나 각 작업이

참고 평가 될 때까지 스레드 풀

  • 기다립니다 : 작업 평가하는 동안, 모든 값은 모두 일정하다. 그러나 스레드는 과거에 작업을 평가했을 가능성이 있으며 캐시 된 값을 유지합니다.

    질문 : 각 스레드가 최신 값을 확인하는 데 필요한 최소한의 동기화 양은 얼마입니까?

    모니터/잠금 관점에서 동기화하는 것을 이해합니다. 캐시/플러시 관점 (즉, 동기화 된 블록의 입/출력시 메모리 모델에서 보장되는 내용)에서 동기화하는 것을 이해하지 못합니다.

    내게는 새 값을 주 메모리에 커밋하기 위해 값을 업데이트하는 스레드에서 한 번 동기화하고 작업자 스레드 당 한 번 캐시를 플러시하여 새 값을 읽을 필요가 있다고 느낍니다. 그러나 나는 이것이 최선의 방법인지 확신 할 수 없다.

    내 방식 : 글로벌 모니터를 만들 : static Object guard = new Object();을 그리고, guard에 동기화, 마스터 목록을 업데이트하는 동안. 그런 다음 스레드 풀을 시작하기 전에 풀의 각 스레드에 대해 한 번 빈 블록의 guard에 동기화하십시오.

    실제로 해당 스레드에서 읽는 값을 모두 플러시 할 수 있습니까? 또는 동기화 블록 내에서 만진 값은 무엇입니까? 어떤 경우에는 빈 블록 대신 루프에서 각 값을 한 번 읽어야합니다.

    감사합니다.


    편집 : 제 생각에는 동기화 된 블록을 종료하면 모든 첫 번째 읽기 (그 시점 이후)가 주 메모리로 이동합니까? 내가 무엇을 동기화했는지에 관계없이?

  • +0

    휘발성 키워드 – ControlAltDel

    +0

    을 사용하기에 거의 완벽한 장소처럼 보입니다. 나는 (사실 상수) 한 번 쓰지만 잠재적으로 수백만 번 읽는 중입니다. 휘발성은 결코 로컬에 캐시되지 않습니다. 매번 스레드 풀을 만든 경우 코드는 괜찮은 승/O 동기화/휘발성 (이전 캐시가 존재하지 않으므로)으로 작동합니다. –

    +0

    나는 여기 휘발성에 대한 필요성을 보지 못했다. ValueRef가 효과적으로 불변 인 경우 실제로 불변으로 만듭니다. Double을 사용하십시오. 작업이 예정되기 전에 각 작업에 대한 새로운 콜렉션을 작성하고이를 (미리 알림으로서) unmodifiableCollection에 랩핑하십시오. 무슨 문제를 생각하니? –

    답변

    3

    과거에 스레드 풀의 스레드가 일부 작업을 평가 한 것은 중요하지 않습니다.Executor

    의 Javadoc는 말한다 :

    메모리 무결성 효과 : 스레드의 작업 이전에 Executor에로의 Runnable 객체를 제출하기 전에 발생의 실행은 아마 다른 스레드에서 시작됩니다.

    따라서 표준 스레드 풀 구현을 사용하고 작업을 제출하기 전에 데이터를 변경하면 메모리 가시성 효과에 대해 걱정할 필요가 없습니다.

    +0

    이것은 작업자 스레드에서 새 작업을 기다리는 동기화 블록이 있기 때문입니다. 그리고 그 블록이 종료되면 쓰레드 전체 캐시가 지워 집니까? 방금 무작위로 동기화하고 같은 효과를 얻을 수 있습니까? –

    +0

    @AndrewRaffensperger : 구현 방법은 중요하지 않습니다. 보증이 제공되며 제공되어야합니다. 마지막 질문에 관해서는 - 기본적으로 그렇습니다.하지만 의미가 없습니다. 동기화의 추가 수단이 없으면 메인 스레드에서 동기화 된 블록 이후에 실행되는 작업자 스레드의 블록을 동기화 할 수 없다고 말할 수 없습니다. 추가적인 동기화 수단을 통해 중복됩니다. – axtavt

    2

    충분한 소리로 계획하고있는 내용. 그것은 "스레드 풀을 깨우는"방법에 달려 있습니다.

    Java 메모리 모델은 synchronized 블록을 입력하기 전에 스레드가 수행 한 모든 쓰기가 해당 잠금에서 이후에 동기화되는 스레드에 표시되도록합니다. 당신이 확신하는 경우

    따라서, 작업자 스레드는 그들이 깨어 났을 때, 마스터 목록을 업데이트하고 실행 가능한이 될 시간 동안의 (a synchronized 블록 안에 있어야합니다)는 wait() 통화 차단, 수정이 만든 마스터 스레드는이 스레드에서 볼 수 있습니다.

    그러나 java.util.concurrent 패키지에 더 높은 수준의 동시성 유틸리티를 적용하는 것이 좋습니다. 이것들은 당신의 솔루션보다 강력 할 것이고, 더 깊이 파고 들기 전에 동시성을 배울 수있는 좋은 장소입니다.


    그냥 명확하게 : 그것은 검사 작업자가 구현하는 작업이 있는지 여부를 확인한다 동기화 된 블록을 사용하지 않고 작업자 스레드를 제어하는 ​​것은 거의 불가능하다. 따라서 컨트롤러 스레드가 작업에 변경 한 사항은 작업자 스레드가 깨어나 기 전에 발생합니다. synchronized 블록 또는 적어도 volatile 변수가 메모리 장벽으로 작동해야합니다. 그러나, 나는 당신이 어떻게 이들 중 하나를 사용하여 스레드 풀을 만들 것이라고 생각하지 못합니다.

    java.util.concurrency 패키지를 사용의 장점의 예로서

    이 고려 : 당신은 그것의 wait() 전화, 또는volatile 변수로 바쁜-대기 루프와 synchronized 블록을 사용할 수 있습니다. 스레드 간의 컨텍스트 전환에 따른 오버 헤드로 인해 특정 조건에서 busy wait가 실제로 더 잘 수행 될 수 있습니다. — 언뜻보기에 끔찍한 생각이 들지는 않습니다.

    동시성 유틸리티 (이 경우는 아마도 ExecutorService)를 사용하는 경우 환경, 작업의 성격 및 다른 스레드의 요구를 고려하여 특정 경우에 가장 적합한 선택을 할 수 있습니다 주어진 시간에. 최적화 수준을 달성하는 것은 불필요한 작업입니다.

    +0

    java.util.concurrent의 오버 헤드를 감당할 수 없습니다. 예제의 데이터는 한 번 업데이트 된 다음 멀티 스레드 평가 중에 "상수"가됩니다. 데이터가 다른 기존 스레드에 표시되는 방식에 관심이 있습니다. 동기화 블록은 동기화 된 모든 발생 전 관계를 사용하지 않아도 이러한 가시성을 유발합니다. 어쩌면 이전에 명시적인 동기화가 필요하지 않고 "모든 값 변경이 이루어지기 전에는 작업이 실행되지 않습니다."라고 계산서가 적합합니다. –

    +1

    @AndrewRaffensperger Right. 그게 전부라면, 정확성을 위해 필요한 최소한의 오버 헤드를 가진'java.util.concurrent' 유틸리티가 있습니다. 동시성 유틸리티의 오버 헤드가 더 높다고 가정하는 것은 실수입니다. 사실, 이들은 compare-and-swap과 같은 고성능 동시성 도구에 대한 액세스를 제공합니다. 자바에서 이것을 직접 구현하는 것은'AtomicXXX' 클래스의 최적화 된 원시 코드보다 느리게 진행될 것입니다. 다른 유틸리티 대부분에서 비슷한 성능 이점이 있습니다. – erickson

    1

    Collection<ValueRef>ValueRef을 컬렉션에 대한 참조를 게시 한 후에 변경할 수 없거나 적어도 컬렉션의 값을 수정하지 않는 이유는 무엇입니까? 그러면 동기화에 대해 걱정할 필요가 없습니다.

    컬렉션의 값을 변경하고 새 컬렉션을 만들고 새 값을 넣으려는 경우입니다. 일단 값이 설정되면 콜렉션 참조 새 작업 객체를 전달하십시오.

    이렇게하지 않는 유일한 이유는 컬렉션의 크기가 너무 커서 메모리에 거의 적합하지 않아 두 개의 복사본을 가질 여유가 없거나 컬렉션을 바꿀 수없는 경우 가비지 수집기 (threaded code에 변경 가능한 데이터 구조를 사용하기 전에 이들 중 하나가 문제가 있음을 증명하십시오).

    +0

    맞습니다. 항상 ValueRef를 재구성하거나 스레드 풀을 재구성 할 수 있으며 문제가 사라집니다. 그러나 실제 구현에서는 데이터 구조가 매우 복잡하며 코드가 자주 호출되어 스레드 풀을 다시 작성하면 각 평가가 너무 많은 오버 헤드가됩니다. –