2015-05-03 27 views
1

我正在開發具有兩個線程:消費者和生產者的循環緩衝區。 我正在使用主動等待Thread.yield。 我知道可以用信號量做到這一點,但我想要沒有信號量的緩衝區。具有線程消費者和生產者的循環緩衝區:它獲得一些執行

兩者都有一個共享變量:bufferCircular。在位置陣列的p

雖然緩衝器未滿的有用的信息,producer寫入數據,並同時有一些有用的信息consumer在位置陣列的c讀取數據。來自BufferCircular的變量nElem是尚未讀取的值數據的數量。

該程序運行相當不錯9/10次運行。然後,有時候,它會在屏幕上顯示最後一個元素(循環數爲500)之前在無限循環中得到填充,或者不顯示任何元素。

我認爲可能是一個liveLock,但我找不到這個錯誤。

共享變量

public class BufferCircular { 
    volatile int[] array; 
    volatile int p; 
    volatile int c; 
    volatile int nElem; 

    public BufferCircular(int[] array) { 
     this.array = array; 
     this.p = 0; 
     this.c = 0; 
     this.nElem = 0; 
    } 

    public void writeData (int data) { 
     this.array[p] = data; 
     this.p = (p + 1) % array.length; 
     this.nElem++; 
    } 

    public int readData() { 
     int data = array[c]; 
     this.c = (c + 1) % array.length; 
     this.nElem--; 
     return data; 
    } 

} 

生產者線程

public class Producer extends Thread { 
    BufferCircular buffer; 
    int bufferTam; 
    int contData; 

    public Productor(BufferCircular buff) { 
     this.buffer = buff; 
     this.bufferTam = buffer.array.length; 
     this.contData = 0; 

    } 

    public void produceData() { 
     this.contData++; 
     this.buffer.writeData(contData); 
    } 

    public void run() { 
     for (int i = 0; i < 500; i++) { 
      while (this.buffer.nElem == this.bufferTam) { 
      Thread.yield(); 
     } 
      this.produceData(); 
     } 
    } 
} 

消費者線程

public class Consumer extends Thread { 
     BufferCircular buffer; 
     int cont; 

     public Consumer(BufferCircular buff) { 
      this.buffer = buff; 
      this.cont = 0; 
     } 

     public void consumeData() { 
      int data = buffer.readData(); 
      cont++; 
      System.out.println("data " + cont + ": " + data); 
     } 

     public void run() { 
      for (int i = 0; i < 500; i++) { 
       while (this.buffer.nElem == 0) { 
       Thread.yield(); 
       } 
       this.consumeData(); 
      } 
     } 
    } 

主要

public class Main { 

    public static void main(String[] args) { 
     Random ran = new Random(); 
     int tamArray = ran.nextInt(21) + 1; 
     int[] array = new int[tamArray]; 

     BufferCircular buffer = new BufferCircular(array); 

     Producer producer = new Producer (buffer); 
     Consumer consumer = new Consumer (buffer); 

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

     try { 
      producer.join(); 
      consumer.join(); 
     } catch (InterruptedException e) { 
      System.err.println("Error with Threads"); 
      e.printStackTrace(); 
    } 

    } 

} 

任何幫助將受到歡迎。

+2

甚至沒有讀完整個問題,但'BufferCircular'充滿了嚴重的線程錯誤。易變數組不會使它們的_elements_變爲volatile,並且在volatile中使用'++'也是一個糟糕的想法(tm)。 「易變」不是一種魔法,它是一個窮人的同步,應該非常小心地使用它。 –

+1

我想補充一點,'數組'在這裏不應該是不穩定的,它應該是私人最終的。首先,您需要在這裏創建'writeData()'和'readData()'方法原子或可序列化。最簡單的方法是聲明他們'同步' –

+0

@SergeyTachenov那麼你還推薦使用'AtomicInteger.incrementAndGet()'而不是'++'? – Shondeslitch

回答

1

您的問題在於您的BufferCircular方法對競爭條件敏感。以writeData()爲例。它執行3個步驟,其中的一些還沒有原子:

this.array[p] = data;    // 1 
this.p = (p + 1) % array.length; // 2 not atomic 
this.nElem++;      // 3 not atomic 

假設2個線程在同一時間進入writeData()。在步驟1中,它們都具有相同的p值,並且都重寫了array[p]的值。現在,array[p]被重寫兩次,並且第一個線程必須寫入的數據丟失,因爲第二個線程寫入同一個索引後。然後他們執行第2步 - 結果是不可預測的,因爲p可以增加1或2(p = (p + 1) % array.length由3個操作組成,其中線程可以交互)。然後,第3步。++運算符也不是原子的:它在幕後使用2個操作。所以nElem也增加1或2.

所以我們有完全不可預知的結果。這導致您的程序執行不力。

最簡單的解決方案是使readData()writeData()方法序列化。對於這一點,聲明它們​​:

public synchronized void writeData (int data) { //... 
public synchronized void readData() { //... 

如果只有一個生產者和一個消費者線程,可以對涉及nElem業務發生競爭條件。解決方案是使用AtomicInteger,而不是int

final AtomicInteger nElem = new AtomicInteger(); 

,並利用其incrementAndGet()decrementAndGet()方法。

+0

但是隻有一個線程(Consumer)輸入'writeData()'。只有一個線程產生,另一個消耗。 – Shondeslitch

+0

即使如此,內部'nElem'變量也是種族敏感的。 '++'和'--'不是原子的。 –

+0

@Shondeslitch使'nElem'' AtomicInteger'而不是'int'並使用它的'incrementAndGet()'和'decrementAndGet'方法。 –

相關問題