2014-07-16 3 views
1

나는 ArrayBlockingQueue가 구현 된 BlockingQueue 인터페이스를 연구 중이었습니다.ArrayBlockingQueue weired behavior를 보여줍니다.

import java.util.concurrent.ArrayBlockingQueue; 
import java.util.concurrent.BlockingQueue; 

public class MainJava { 
    public static void main(String[] args) { 
     BlockingQueue<String> queue = new ArrayBlockingQueue<>(1); 
     Producer producer = new Producer(queue); 
     Consumer consumer = new Consumer(queue); 
     new Thread(producer).start(); 
     new Thread(consumer).start(); 
    } 
} 

class Producer implements Runnable { 
    BlockingQueue<String> queue = null; 

    public Producer(BlockingQueue<String> queue) { 
     this.queue = queue; 
     // TODO Auto-generated constructor stub 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10; i++) { 
      try { 
       System.out.println("Producer added " + i); 
       queue.put(String.valueOf(i)); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

class Consumer implements Runnable { 
    BlockingQueue<String> queue = null; 

    public Consumer(BlockingQueue<String> queue) { 
     this.queue = queue; 
    } 

    @Override 
    public void run() { 
     for (int i = 0; i < 10; i++) { 
      try { 
       System.out.println("Consumer used " + queue.take()); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 
} 

예상되는 동작

Producer add 0 
Consumer used 0 
Producer add 1 
Consumer used 1 
and so on.. 

Actaul O/P

Producer added 0 
Producer added 1 
Consumer used 0 
Consumer used 1 
Producer added 2 
Producer added 3 
Producer added 4 

나는 그것이 제대로 작동 디버그 모드를 사용하여 시도 : 데모 목적을 위해 내가 개발할 다음 코드가 . 그렇다면 왜 디버그 모드가 없으면 제대로 작동하지 않는 것입니까? 당신의 생산에서

: 그것은 당신의 대기열이 불운 타이밍에 의한 한 번에 두 개 이상의 항목을 포함 할 것 같은 '환상'과 잘못된 장소에서 System.out.println입니다 -

+0

두 스레드가 서로 병렬로 실행 중이기 때문에 이러한 현상이 발생합니다! 그들의 행동을 예측할 수는 없습니다! – ItachiUchiha

+0

@ItachiUchiha 실제로 그는 크기 1의 블로킹 큐를 가지고있어서 항상 대기하고 있습니다. 여기서 "문제"는 System.out.println의 출력을 신뢰하여 올바르게 작동하는지 여부를 결정하는데, 신뢰할 수는 없습니다. – Kayaman

+0

내 두 스레드는 하나의 항목을 추가하는 다른 항목을 추가하는 que를 사용합니다. 생산자가 하나의 항목을 추가하고 차단해야하는 다른 항목을 추가하려고하면 que가 비어있을 때까지 항목이 대기열에 추가 될 때까지 차단해야합니다. BlockingQueue를 올바르게 사용할 수 있습니까? – user3608352

답변

2

코드는 예상대로 작동 System.out.println은 차단 put 앞에 오기 때문에 put이 대기열이 비워 질 때까지 기다려야하는 경우에도 'Producer added x'가 인쇄됩니다. 우리가합니다 (putSystem.out.println 전에 오는 가짐)이 두 줄을 교환 할 경우에도

는하지만, 출력이 잘못된 것 같다

Consumer used 0 
Producer added 0 
Producer added 1 
Consumer used 1 
Consumer used 2 
Producer added 2 
Producer added 3 
Consumer used 3 

아직도,이 불운 한 타이밍에 의한 환상이다. 당신이 볼 수 있듯이, 내 산출물에서 소비자는 전에 0 요소를 소비하는 것처럼 보입니다. 생산자가 대기열에 넣었습니다! 이것은 물론 그렇지 않습니다. System.out은 스레드 세이프 인 PrintStream 개체입니다. 따라서 한 번에 하나의 스레드 만이 무언가를 동시에 인쇄 할 수 있습니다. 위의 실행에서 소비자 스레드는 단순히 제작자 이전에 잠금을 잡았습니다.

실행 순서는 그런 일이 될 수 있습니다

  1. 프로듀서 : put 0 into queue
  2. 소비자 : take 0 from queue
  3. 소비자 : print 'Consumer used 0'
  4. 소비자 : wait until queue not empty
  5. 프로듀서 : print 'Producer added 0'
  6. 생산자 : put 1 into queue
  7. 프로듀서 : print 'Producer added 1'
  8. 프로듀서 : wait until queue not full
  9. 소비자 : take 1 from queue
  10. 소비자 : print 'Consumer used 1'
  11. 프로듀서 : put 2 into queue
  12. 소비자 : take 2 from queue
  13. 소비자 : print 'Consumer used 2'
  14. 프로듀서 : print 'Producer added 2'

그것은 올바른 행동에 대해 주장하는 멀티 스레드 시스템에서 매우 어려운

은 ...

2

@Kayaman 실제로 이미 질문에 대답,하지만 어쩌면 조금 더 정보는 여기에 도움이 될 수 있습니다.

EDIT ... 및 isnot2bad는 (+1) 비슷한 답변을 썼습니다. 나는 너무 느리다. 그러나 그럼에도 불구하고 유용하다고 생각할 수도 있습니다. 모든

첫째 : 분명히 실제로 큐에 추가 된 전에 Producer added 1을 인쇄 할 수있는 코드

System.out.println("Producer added " + i); queue.put(String.valueOf(i)); 

. 회선을 바꾸는 것만으로도 출력이 잘못된 것처럼 보이지만 완전히 제거하지 않는 경우의 수가 줄어 듭니다.

queue.put(String.valueOf(i)); 
System.out.println("Producer added " + i); 

그 이유는이 두 줄이 원자이 아니기 때문입니다.

각 스레드는 각 단계에서 무엇을하고 있는지 보여줍니다 예 일정 결과가 임의로 잘못보고 작업의 순서에 따라 수 있음을 보여줍니다 : 아직도

Producer thread:   Consumer thread:   Queue:  Output: 
------------------------------------------------------------------------ 
queue.put(0);          [0] 
println("Added " + 0);        []   Added 0 
          String 0 = queue.take(); [] 
          println("Used " + 0);  []   Used 0 
queue.put(1);          [1]   
          String 1 = queue.take(); [] 
println("Added " + 1);        []   Added 1 
queue.put(2);          [2]   
println("Added " + 2);        [2]   Added 2 
          println("Used " + 1);  [2]   Used 1 
          String 2 = queue.take(); [] 
          println("Used " + 2);  []   Used 2 
queue.put(3);          [3]   
          String 3 = queue.take(); [] 
          println("Used " + 3);  []   Used 3 
println("Added " + 3);        []   Added 3 
queue.put(4);          [4]   
println("Added " + 4);        [4]   Added 4 

, 키 포인트는 큐 결코 둘 이상의 요소를 포함합니다.

+0

+1 좋은 타이밍 테이블! 특정 단계를 병렬 처리 할 수도 있습니다. 생산자의 처음 6 단계는 하나의 흐름 (빈 줄 제외)에서 수행 될 수 있지만 소비자는 첫 번째 단계를 한 블록 (한 단계 지연)로 실행합니다. – isnot2bad