2014-11-17 8 views
8

Java의 "primitives"(synchronized, wait(), notify())를 사용하여 Java의 바운드 BlockingQueue 인터페이스와 유사한 것을 구현하려고했지만 이해가 안되는 동작을 발견했습니다.자바 : 대기 중 2 개 + 블로킹 된 1 개 스레드, notify()는 라이브 록으로 연결되며 notifyAll()은 그렇지 않습니다.

하나의 요소를 저장할 수있는 큐를 만들고 큐에서 값을 가져 오기 위해 대기하는 두 개의 스레드를 만들고 시작한 다음 주 스레드의 동기화 된 블록에있는 큐에 두 개의 값을 넣으려고합니다. 대부분의 경우 작동하지만 때로는 값을 기다리는 두 개의 스레드가 겉보기에는 서로 깨어나서 주 스레드가 동기화 된 블록에 들어 가지 않게하기 시작합니다.

가 여기 내 (간체) 코드입니다 :

import java.util.LinkedList; 
import java.util.Queue; 

public class LivelockDemo { 
    private static final int MANY_RUNS = 10000; 

    public static void main(String[] args) throws InterruptedException { 
     for (int i = 0; i < MANY_RUNS; i++) { // to increase the probability 
      final MyBoundedBlockingQueue ctr = new MyBoundedBlockingQueue(1); 

      Thread t1 = createObserver(ctr, i + ":1"); 
      Thread t2 = createObserver(ctr, i + ":2"); 

      t1.start(); 
      t2.start(); 

      System.out.println(i + ":0 ready to enter synchronized block"); 
      synchronized (ctr) { 
       System.out.println(i + ":0 entered synchronized block"); 
       ctr.addWhenHasSpace("hello"); 
       ctr.addWhenHasSpace("world"); 
      } 

      t1.join(); 
      t2.join(); 

      System.out.println(); 
     } 
    } 

    public static class MyBoundedBlockingQueue { 
     private Queue<Object> lst = new LinkedList<Object>();; 

     private int limit; 

     private MyBoundedBlockingQueue(int limit) { 
      this.limit = limit; 
     } 

     public synchronized void addWhenHasSpace(Object obj) throws InterruptedException { 
      boolean printed = false; 
      while (lst.size() >= limit) { 
       printed = __heartbeat(':', printed); 
       notify(); 
       wait(); 
      } 
      lst.offer(obj); 
      notify(); 
     } 

     // waits until something has been set and then returns it 
     public synchronized Object getWhenNotEmpty() throws InterruptedException { 
      boolean printed = false; 
      while (lst.isEmpty()) { 
       printed = __heartbeat('.', printed); // show progress 
       notify(); 
       wait(); 
      } 
      Object result = lst.poll(); 
      notify(); 
      return result; 
     } 

     // just to show progress of waiting threads in a reasonable manner 
     private static boolean __heartbeat(char c, boolean printed) { 
      long now = System.currentTimeMillis(); 
      if (now % 1000 == 0) { 
       System.out.print(c); 
       printed = true; 
      } else if (printed) { 
       System.out.println(); 
       printed = false; 
      } 
      return printed; 
     } 
    } 

    private static Thread createObserver(final MyBoundedBlockingQueue ctr, 
      final String name) { 
     return new Thread(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        System.out.println(name + ": saw " + ctr.getWhenNotEmpty()); 
       } catch (InterruptedException e) { 
        e.printStackTrace(System.err); 
       } 
      } 
     }, name); 
    } 
} 

가 여기에 내가 무엇을보고 때 그것은 "블록"그러나

(skipped a lot) 

85:0 ready to enter synchronized block 
85:0 entered synchronized block 
85:2: saw hello 
85:1: saw world 

86:0 ready to enter synchronized block 
86:0 entered synchronized block 
86:2: saw hello 
86:1: saw world 

87:0 ready to enter synchronized block 
............................................ 

.......................................................................... 

.................................................................................. 
(goes "forever") 

, 나는() (그동안 내부에서 호출이 통지 변경하는 경우. ..) addWhenHasSpace 및 getWhenNotEmpty 메소드의 notifyAll() 루프는 "항상"통과합니다.

제 질문은이 경우입니다 :이 경우 notify()와 notifyAll() 메소드간에 동작이 다르며 notify()의 동작이 왜 그런가?

  1. 는이 경우의 notifyAll()는 일어나 것이라고 나에게 보인다 때문에

    나는 (두 스레드가 하나가 BLOCKED 대기) 두 가지 방법이 경우에 같은 방식으로 행동 기대 다른 스레드는 notify()와 동일합니다.

  2. 스레드를 깨우는 방법의 선택에 따라 깨어나는 스레드 (예 : RUNNABLE 추측)와 기본 스레드 (차단 된 스레드)가 달라집니다. 나중에은 잠금을 위해 경쟁합니다. 내가 javadoc에서 기대할 수있는 것뿐만 아니라 주제에 관한 인터넷 검색.

어쩌면 내가 뭔가 잘못하고있는 것일까 요?

+4

왜'notify()'*와 *'wait()'를 반복하여 호출합니까? 두 개의 모니터가 필요합니다. 하나는 "소비 할 항목이 있고"다른 하나는 "채워질 공간이 있습니다"입니다. –

+0

고마워요, 당신은 내가 바보 같은 일을 끊임없이 일어나고있는 것을 깨닫게했습니다. 나는 문제에 집중하고 (명백한) 더 나은 방법을 보지 못했다. 아직도 notify()와 notifyAll()이이 경우 다르게 동작하는 이유는 무엇입니까?하지만 작동하는 훨씬 더 좋은 방법이 있으므로이 질문은 단지 이론적 인 흥미에 불과합니다. – starikoff

답변

2

내재 된 잠금을 사용하여 공정성/충전 중임이 나타나는 것으로 보입니다. 아마도 최적화 때문일 수 있습니다. 나는 네이티브 코드가 현재 쓰레드가 모니터를 기다렸다가 기다려야하는지 알기 위해 검사를하고 그것이 성공할 수 있는지를 추측하고있다.

synchronizedReentrantLock으로 대체하면 예상대로 작동합니다. 다른 점은 ReentrantLock이 통지 한 잠금의 웨이터를 처리하는 방법입니다.


업데이트 :

흥미 롭 여기서 찾을 수 있습니다. 당신이보고있는 것은 다른 두 개의 스레드가 각각 synchronized 지역을 입력하면서

 synchronized (ctr) { 
      System.out.println(i + ":0 entered synchronized block"); 
      ctr.addWhenHasSpace("hello"); 
      ctr.addWhenHasSpace("world"); 
     } 

들어가는 main 스레드 사이의 경쟁이다. 주 스레드가 두 영역 중 적어도 하나보다 먼저 동기화 영역에 들어 가지 않으면 설명하는 라이브 잠금 출력이 발생합니다.

두 소비자 스레드가 모두 동기 블록을 히트하면 그들은 notifywait에 대해 서로 핑 (ping)합니다. JVM이 스레드가 차단되는 동안 모니터에 우선 순위를 대기중인 스레드를 제공하는 경우 일 수 있습니다.

+1

* "스레드가 차단되어있는 동안 JVM이 모니터에 우선 순위를두고 대기중인 스레드를 제공하는 경우 일 수 있습니다."* - 이와 같이 보이지만 notify()가 사용될 때만 해당합니다. notifyAll) 아마 어떤 방식 으로든 블럭 주 스레드에 영향을 미치지 않습니다 ... – starikoff

+0

@starikoff 그게 사실입니다 네이티브 코드에 깊이 잠수없이 나는 의심 할 여지없이 우리가'notifyAll'이 실제로 스레드와 제안을 릴리스한다고 가정 할 수 있다고 생각합니다 우선 순위 없음. –

1

코드를 너무 자세히 살펴 보지 않고 하나의 조건 변수를 사용하여 하나의 생성자와 둘 이상의 소비자가 포함 된 대기열을 구현하고 있음을 알 수 있습니다. 문제 해결법 : 하나의 조건 변수 만있는 경우 소비자가 notify()을 호출하면 생산자를 깨우거나 다른 소비자를 깨울 것인지 여부를 알 수 없습니다.

은 트랩의 두 가지 방법이 있습니다 : 간단한 항상 notifyAll().

다른 방법은 synchronized, wait()notify() 사용을 중지하는 것입니다 사용하고, 대신 java.util.concurrent의에서 시설을 사용하는 것입니다. 자물쇠.

단일 ReentrantLock 개체는 두 가지 (또는 그 이상) 조건 변수를 제공 할 수 있습니다. 하나는 생산자에게 독점적으로 사용하여 소비자에게 알리고 다른 하나는 소비자에게 독점적으로 사용하여 생산자에게 알립니다.

참고 : ReentrantLocks를 사용하여 전환하면 이름이 변경됩니다. o.wait()c.await()이되고 o.notify()c.signal()이됩니다.