2013-07-19 2 views
1

자바에서는 아래 코드를 사용하여 간단한 대기 및 notifyAll() 메서드를 사용하여 생성자 및 소비자 구현을 작성하려고했습니다. 몇 초 동안 실행되고 나중에 중단됩니다. 모든 문제를 해결하는 방법.생산자 소비자의 교착 상태를 해결하는 방법

import java.util.ArrayDeque; 
import java.util.Queue; 

public class Prod_consumer { 
    static Queue<String> q = new ArrayDeque(10); 

    static class Producer implements Runnable { 
     public void run() { 
      while (true) { 
       if (q.size() == 10) { 
        synchronized (q) { 
         try { 
          System.out.println("Q is full so waiting"); 
          q.wait(); 
         } catch (InterruptedException ex) { 
          ex.printStackTrace(); 
         } 
        } 
       } 
       synchronized (q) { 
        String st = System.currentTimeMillis() + ""; 
        q.add(st); 
        q.notifyAll(); 
       } 
      } 
     } 

    } 

    static class Consumer implements Runnable { 
     public void run() { 
      while (true) { 
       if (q.isEmpty()) { 
        synchronized(q) { 
         try { 
          System.out.println("Q is empty so waiting "); 
          q.wait(); 
         }catch(InterruptedException ie) { 
          ie.printStackTrace(); 
         } 
        } 
       } 
       synchronized(q) { 
        System.out.println(q.remove()); 
        q.notifyAll(); 
       } 

      } 

     } 
    } 

    public static void main(String args[]) { 
     Thread consumer = new Thread(new Consumer()); 
     Thread consumer2 = new Thread(new Consumer()); 
     Thread producer = new Thread(new Producer()); 

     producer.start(); 
     consumer.start(); 
     consumer2.start(); 

    } 

} 

답변

2

귀하의 Producer 코드가 의심스러운 당신의 소비자에 같은 문제가 있습니다. 대기열 크기가 10 이하가 될 때까지 기다렸다가 다음 요소를 추가하려고합니다.그러나 현재 논리를 사용하면 이유에 관계없이 알림이 나올 때까지 대기하고, 대기열이 용량을 초과하는지 여부를 확인한 다음 대기열에서 잠금을 해제합니다. 그런 다음 큐를 다시 잠그고 항목을 추가합니다 (다른 스레드가 큐에 항목을 넣었는지 여부에 관계없이).

static class Producer implements Runnable { 
    public void run() { 
     while (true) { 
      synchronized (q) { 
       if (q.size() < 10) { 
        String st = System.currentTimeMillis() + ""; 
        q.add(st); 
        q.notifyAll(); 
       } else { 
        try { 
         System.out.println("Q is full so waiting"); 
         q.wait(); 
        } catch (InterruptedException ex) { 
         ex.printStackTrace(); 
        } 
       } 
      } 
     } 
    } 
} 

당신은 Consumer 클래스와 비슷한 문제가 있습니다

는이 코드를 제안한다. 두 경우 모두, 잠금이 진행 괜찮 및 방법의 실제 사업 여부를 확인 코드 사이에 유지되는 것을

static class Consumer implements Runnable { 
    public void run() { 
     while (true) { 
      synchronized (q) { 
       if (q.isEmpty()) { 
        try { 
         System.out.println("Q is empty so waiting "); 
         q.wait(); 
        }catch(InterruptedException ie) { 
         ie.printStackTrace(); 
        } 
       } else { 
        System.out.println(q.remove()); 
        q.notifyAll(); 
       } 
      } 
     } 
    } 
} 

참고 :이 제안.

+0

하나 사소한 문제는 내가 제기하고자합니다. 일반적으로 스레드가 대기 상태가되면, 누군가가 알릴 때까지 획득 한 해당 모니터를 포기합니다. wait()에서 깨어 나면 스레드는 이미 모니터를 획득하게됩니다. 그러나 당신의 접근 방식은 (싱크 블록을 떠나서) 획득 한 모니터를 포기하고, 여전히 (내가 인식 할 수있는 것에서 작동하지만) 여분의 불필요한 부담을 가중시키는 다시 획득합니다. –

+0

@AdrianShum - 문제를 적어두면 좋습니다. 생산자 스레드 또는 소비자 스레드가 여러 개인 경우'synchronized' 블록을 남겨두면 각각의 큐에 약간의 큐를 사용할 기회가 주어집니다. 'while' 블록이'synchronized' 블록 안에 있다면, 일단 생산자 나 소비자가 모니터를 얻으면 대기열이 용량에 있거나 비어있을 때까지 기다리게됩니다 (해당되는 경우). 이것이 원하는 동작이면 중첩 순서를 뒤집는 것이 적절할 것입니다. –

+0

심지어 루프를 sync 블록으로 이동해도 대기열이 비어 있거나 비울 때까지 모니터를 유지하지 않습니다. 일단 wait()로 이동하면 모니터가 해제되고 다른 스레드는 여전히 "대기열 사용"이 가능합니다. 유일한 차이점은 모니터를 해제하고 다시 가져 오는 오버 헤드입니다. –

1

현재 구현에 많은 문제가 있음을 알 수 있습니다. 그러나 조사한 내용과 죽은 자물쇠가 어디에서 발생 했습니까? 나는 이것이 당신이 한 일이라고 믿습니다.

가장 큰 문제 중 하나는 동기화의 범위가 잘못되어 단순히 많은 경쟁 조건이 발생한다는 것입니다.

예를 들어 컨슈머 로직을 사용하면 대기열에 요소가 하나만있을 가능성이 있습니다. 소비자 스레드 모두 if (q.isEmpty()) {에 도달했으며 둘 다 대기열에서 가져 오는 것이 있다고 생각합니다. 그렇다면 두 가지 모두 계속 진행되어 q.remove()을 실행합니다. 첫 번째 스레드에서는 문제가 없지만 다음 스레드에서는 예외가 throw됩니다.

경쟁 조건의 또 다른 예는 대기열을 확인한 소비자가 비어있을 수도 있지만 동기화 블록을 시작하기 직전에 생산자가 전체 항목을 대기열에 넣은 다음 소비자가 wait(). notifyAll()이 이전에 완료되었으므로 소비자는 이전 notifyAll()을 잃어 버리고, 이제는 대기열이 가득 차서 생산자 스레드가 대기열에서 누군가가 소비하기를 기다리는 동안 새 항목을 대기열에 넣지 않기 때문에. 붐, 교착 상태

코드에 다른 문제가 있습니다 (예 : 루프에서 대기 줄 바꿈() 안 함).

나는 생산자 - 소비자 대기열의 몇 가지 예를 들어 Google에 강력하게 제안합니다. (나는 톤이 있다고 믿습니다.) 올바른 방법은 무엇인지 이해하려고합니다. @에 TedHopp의 코멘트 의견을 회신에 대한


: 작동 TedHopp의 방법 @

을하지만 해제하고 큐의 모니터를 재 취득 할 필요가 없습니다. 또한

static class Producer implements Runnable { 
    public void run() { 
     while (true) { // keep on adding item 
      String st = "" + System.currentTimeMillis(); // prepare the item 
      synchronized (q) { 
       while (q.size() >= 10) { // keep on waiting when the queue is full 
        try { 
         System.out.println("Q is full so waiting"); 
         q.wait(); 
        } catch (InterruptedException ex) { 
         ex.printStackTrace(); 
         // should be properly handled by rethrowing etc. 
        } 
       } 
       q.add(st); // add item to queue as it is not full at this moment 
       q.notifyAll(); 
      } 
     } 
    } 
} 

"추가"하지만, 일반적으로 우리는 논리를 포함, 생산자 - 소비자 큐 클래스를 생성하는 등의 PC-큐의 방법을 원하는 것 :

일반적으로 그것이 있어야 뭔가처럼 보인다 위의 동기화 블록 내에서

위의 방법은 모니터의 추가 릴리스/재 획득을 필요로하지 않으므로 pc 대기열에서 구현해야하는 것이 더 가깝습니다.

0

대기열에 닿은 코드를 모두 동기화해야합니다. 예를

if (q.size() == 10) { 
    synchronized (q) { 

은 if 문을 평가 한 후 큐 크기가 변경 될 수 있습니다에 대한 . 주문을 변경하는 것이 좋습니다.

당신은

1

공유 메모리의 모든 작업은 다중 스레드 액세스에 대해 보호되어야합니다. 현재 구현은 대기열 검사의 상태가 비 동기화되어 교착 상태가 발생합니다. 교착 상태 시나리오를 얻으려면 Promela에서 해당 코드를 쉽게 모델링 할 수 있어야합니다. 그럼에도 불구하고 condition_variables (synchronized 섹션)에는 카운팅 의미가 없다는 것을 알고 있어야합니다. 따라서 스레드가 대기 상태에 도달하기 전에 선점되어 있고 다른 스레드가 notifyAll() 함수를 호출하면 그 이후에 선점 된 스레드에는 적용되지 않습니다 컨트롤을 다시 받으십시오. 이 솔루션은 매우 간단합니다 :

... 
while (true) 
{ 
    synchronized (q) 
    { 
     if (q.size() == 10) 
... 
while (true) 
{ 
    synchronized(q) 
    { 
     if (q.isEmpty()) 
... 
관련 문제