2014-10-24 4 views
6

설명서에 객체가 스레드 안전하다고 표시되어 있지만 모든 메소드의 모든 액세스가 스레드로부터 안전하다는 것을 알고 있습니다. 그래서 한번에 많은 쓰레드에서 put()을 호출하고 같은 인스턴스에서 그걸 가져온다면 아무 일도 일어나지 않을까요? 이 대답은 나에게 두 번째 추측을하고 있기 때문에BlockingQueue는 Java에서 완전히 스레드 안전합니까?

내가 물어 : https://stackoverflow.com/a/22006181/4164238

답변

22

빠른 대답은 '예, 그들은 스레드로부터 안전합니다. 그러나 거기에 두지 마십시오. ...

작은 집 보관, BlockingQueue은 인터페이스이며 스레드로부터 안전하지 않은 모든 구현은 문서화 된 계약을 위반하게됩니다. 포함 된 링크는 LinkedBlockingQueue을 언급하고 있는데, 약간의 영리성이 있습니다.

link that you included은 재미있는 관찰을합니다. 예. LinkedBlockingQueue 내에 두 개의 잠금이 있습니다. 그러나 '단순한'구현이 파울을 범하는 에지 케이스가 실제로 처리된다는 것을 이해하지 못합니다. 따라서 테이크 및 푸시 메소드가 처음 예상했던 것보다 더 복잡합니다.

LinkedBlockingQueue은 읽기 및 쓰기에서 동일한 잠금을 사용하지 않도록 최적화되어 있지만 충돌이 줄어들지 만 올바른 동작을 위해서는 비어 있지 않은 큐를 사용합니다. 큐가 그 안에 요소를 가지고 있으면 푸시와 팝 포인트는 같은 메모리 영역에 있지 않으므로 경합을 피할 수 있습니다. 그러나 대기열이 비어있는 경우 경합을 피할 수 없으므로이 일반적인 '가장자리'의 경우를 처리하는 데 추가 코드가 필요합니다. 이는 코드 복잡성과 성능/확장 성 간의 일반적인 상충 관계입니다.

다음 질문은 다음과 같습니다. LinkedBlockingQueue은 대기열이 비어 있거나 비어 있지 않은 것을 어떻게 알 수 있습니까? 그러면 스레딩을 처리 할 수 ​​있습니까? 대답은 두 개의 추가 동시 데이터 구조로 AtomicIntegerCondition을 사용한다는 것입니다. AtomicInteger은 큐의 길이가 0인지 여부와 큐가 아마도 원하는 상태에있을 때 신호가 대기 스레드에 알리기를 기다리는 데 사용되는 조건을 확인하는 데 사용됩니다. 이 추가 조정에는 오버 헤드가 있지만 측정시 동시 스레드 수를 늘리면이 기술의 오버 헤드가 단일 잠금을 사용하여 도입 된 경합보다 낮음을 알 수 있습니다.

아래 코드를 LinkedBlockingQueue에서 복사하고 작동 방식을 설명하는 주석을 추가했습니다. 높은 수준에서 take()은 먼저 다른 모든 호출을 take()으로 잠근 다음 필요에 따라 put()을 신호로 보냅니다. put()도 비슷한 방식으로 작동합니다. 먼저 다른 모든 호출을 put()으로 차단 한 다음 필요한 경우 take()에 신호를 보냅니다. put() 방법에서

:

// putLock coordinates the calls to put() only; further coordination 
    // between put() and take() follows below 
    putLock.lockInterruptibly(); 
    try { 
     // block while the queue is full; count is shared between put() and take() 
     // and is safely visible between cores but prone to change between calls 
     // a while loop is used because state can change between signals, which is 
     // why signals get rechecked and resent.. read on to see more of that 
     while (count.get() == capacity) { 
       notFull.await(); 
     } 

     // we know that the queue is not full so add 
     enqueue(e); 
     c = count.getAndIncrement(); 

     // if the queue is not full, send a signal to wake up 
     // any thread that is possibly waiting for the queue to be a little 
     // emptier -- note that this is logically part of 'take()' but it 
     // has to be here because take() blocks itself 
     if (c + 1 < capacity) 
      notFull.signal(); 
    } finally { 
     putLock.unlock(); 
    } 
    if (c == 0) 
     signalNotEmpty(); 

대답은 조금 이상한 것을

takeLock.lockInterruptibly(); 
    try { 
      // wait for the queue to stop being empty 
      while (count.get() == 0) { 
       notEmpty.await(); 
      } 

     // remove element 
     x = dequeue(); 

     // decrement shared count 
     c = count.getAndDecrement(); 

     // send signal that the queue is not empty 
     // note that this is logically part of put(), but 
     // for thread coordination reasons is here 
     if (c > 1) 
      notEmpty.signal(); 
    } finally { 
     takeLock.unlock(); 
    } 
    if (c == capacity) 
     signalNotFull(); 
+0

알았어, 알았어. 만약 내가'BlockingQueue를 할 때처럼 들린다. killAllMailbox = new LinkedBlockingQueue ();'나는 안전하다? – Josh

+0

@ Josh 예, 당신은 분명히 있습니다. 이 클래스는 많은 사람들이 잘 테스트 해 왔습니다. 드문 경우이지만, 문제가 발생했다면 JDK 버그 일 것이고 Java 유지 보수 담당자는 문제를 해결할 것입니다. 하지만 안심할 수 있습니다. 몇 년 동안 잘 테스트되고 사용되었습니다. –

+0

그런 세부 사항에 들어가 주셔서 감사합니다! – Josh

0

take()에서 - 그것은 어떤 잠금이없는, 그래서 시작을 위해이 인터페이스 BlockingQueue는 인터페이스입니다. ArrayBlockingQueue와 같은 구현은 add() 및 take()에 대해 동일한 잠금을 사용하므로 좋을 것입니다. 일반적으로, 구현이 thread 세이프가 아닌 경우는 버그가있는 구현이됩니다.

1

예. BlockingQueue의 모든 구현은 put 및 take 및 모든 동작에 대해 스레드로부터 안전합니다.

링크가 중간에 오르고 ... 자세한 내용은 다루지 않습니다. 스레드가 안전한지 확인하십시오.

관련 문제