2014-11-18 3 views
0

article은 잠금 경합을 줄이는 아이디어 인 "Double-Checked Locking"에 대해 설명합니다. 기사에서 설명하는대로 작동하지 않습니다. "(여전히) 깨진 다중 스레드 버전"이중 확인 잠금 "관용구"표의 코드 샘플을 참조하십시오.이 이중 확인 잠금 장치가 손상되지 않았습니까?

이제는 작동해야하는 변형을 발견했다고 생각합니다. 문제는 그것이 올바른지 여부입니다. 공유 대기열을 통해 데이터를 교환하는 소비자와 생산자가 있다고 가정 해 보겠습니다.

class Producer { 
    private Queue queue = ...; 
    private AtomicInteger updateCount; 

    public void add(Data data) { 
     synchronized(updateCount) { 
      queue.add(task); 
      updateCount.incrementAndGet(); 
     } 
    } 
} 

class Consumer { 
    private AtomicInteger updateCount = new AtomicInteger(0); 
    private int updateCountSnapshot = updateCount.get(); 

    public void run() { 
     while(true) { 
      // do something 
      if(updateCountSnapshot != updateCount.get()) { 
       // synchronizing on the same updateCount 
       // instance the Producer has 
       synchronized(updateCount) { 
        Data data = queue.poll() 
        // mess with data 
        updateCountSnapshot = updateCount.get(); 
       } 
      } 
     } 
    } 
} 

질문은이 접근 방식이 효과가 있다고 생각하는지 여부입니다. 나는 많은 사람들이 깨지기 때문에 깨지기 때문에 확신하고 싶습니다 ... 그 생각은 updateCount가 변경되면 소비자에게 동기화 된 블록을 입력 할 때 잠금 경합을 줄이는 것입니다.

+3

왜 휠을 다시 발명하려고하십니까? 생성자와 소비자 사이의 의사 소통을위한 잘 알려진 thread-safe 구조가 있는데 그것은 BlockingQueue입니다. – OldCurmudgeon

+0

왜냐하면 소비자를 실행하는 스레드는 블로킹을 할 때 할 수없는 다른 작업을 수행하기 때문에 바쁘기 때문입니다 갖다. 주기적으로 poll()을 호출하는 것은 너무 똑똑하지도 않습니다 (또한 폴링은 일부 CPU주기를 먹습니다). – OlliP

+0

그런 다음 동시성 문제가 아닌 아키텍처 문제가 있습니다. 'poll '을 주기적으로 호출하는 데는 아무런 문제가 없습니다. 여러분은 조기에 최적화 할 수 있습니다. – OldCurmudgeon

답변

3

Code Review에 대해 더 알고 싶습니다.

는 다음과 같은 사항을 고려해야합니다

  • 이되지 재확인 잠금입니다.
  • 데이터가 도착하지 않으면 소비자가 아무 것도하지 않고 CPU를 먹을 것입니다.
  • AtomicInteger를 세마포로 사용합니다.
  • BlockingQueue가이 모든 작업을 수행합니다.
  • updateCount이 공유되었는지 제대로 확인하지 않았습니다.
  • 원자 단위로 동기화 할 필요는 없습니다.

다음은 간단한 데모 용 프로듀서/소비자 쌍입니다.

public class TwoThreads { 

    public static void main(String args[]) throws InterruptedException { 
     System.out.println("TwoThreads:Test"); 
     new TwoThreads().test(); 
    } 

    // The end of the list. 
    private static final Integer End = -1; 

    static class Producer implements Runnable { 

     final Queue<Integer> queue; 

     public Producer(Queue<Integer> queue) { 
      this.queue = queue; 
     } 

     @Override 
     public void run() { 
      try { 
       for (int i = 0; i < 1000; i++) { 
        queue.add(i); 
        Thread.sleep(1); 
       } 
       // Finish the queue. 
       queue.add(End); 
      } catch (InterruptedException ex) { 
       // Just exit. 
      } 
     } 

    } 

    static class Consumer implements Runnable { 

     final Queue<Integer> queue; 

     public Consumer(Queue<Integer> queue) { 
      this.queue = queue; 
     } 

     @Override 
     public void run() { 
      boolean ended = false; 
      while (!ended) { 
       Integer i = queue.poll(); 
       if (i != null) { 
        ended = i == End; 
        System.out.println(i); 
       } 
      } 
     } 

    } 

    public void test() throws InterruptedException { 
     Queue<Integer> queue = new LinkedBlockingQueue<>(); 
     Thread pt = new Thread(new Producer(queue)); 
     Thread ct = new Thread(new Consumer(queue)); 
     // Start it all going. 
     pt.start(); 
     ct.start(); 
     // Wait for it to finish. 
     pt.join(); 
     ct.join(); 
    } 

} 
+0

무슨 뜻인지 알 겠어. 당신이 가져간 노력에 감사드립니다. 사실, 당신이 위에서 일한 것처럼 뭔가를 개발하는 것이 어렵지 않습니다. 그럼에도 불구하고 당신의 노력에 감사드립니다. 내 게시물은 주로 성능 최적화에 관한 내용이었습니다. 이제 저는 약간의 측정을 수행했고 놀랍게도 AtomicInteger.incrementAndGet은 LinkedTransferQueue.poll보다 더 많은 시간을 필요로합니다! 나는 투표가 많은 일을한다고 확신했기 때문에 매우 놀랐다. 그래서, 상황은 벌써 잘되어 있습니다. 더 이상 애도가 필요하지 않습니다. – OlliP

+0

@ollip - 다른 연습보다 최적화의 이름으로 더 악행되었습니다. - Albert Fortran. – OldCurmudgeon

+0

멋진 인용문 :-). 이 앨버트 포트란이 실제로 존재합니까 아니면 유머러스합니까? – OlliP