2

I'm developing a circular buffer with two Threads: Consumer and Producer. I'm using active waiting with Thread.yield. I know that it is possible to do that with semaphores, but I wanted the buffer without semaphores.

Both have a shared variable: bufferCircular.

While the buffer is not full of useful information, producer write data in the position pof array, and while there are some useful information consumer read data in the position c of array. The variable nElem from BufferCircular is the number of value datas that haven't been read yet.

The program works quite good 9/10 times that runs. Then, sometimes, it get stucks in a infinite loop before show the last element on screen (number 500 of loop for), or just dont' show any element.

I think is probably a liveLock, but I can't find the mistake.

Shared Variable:

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;
    }

}

Producer Thread:

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();
       }
    }
}

Consumer Thread:

    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();
           }
        }
   }

Main:

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();
    }

    }

}

Any help will be welcome.

Shondeslitch
  • 1,049
  • 1
  • 13
  • 26
  • 2
    Didn't even read the full question, but `BufferCircular` is full of serious threading errors. Volatile arrays don't make their _elements_ volatile, and using `++` on volatiles is a Bad Idea(tm) too. `volatile` is not a magic spell, it is a poor man's synchronization which should be used very carefully. – Sergei Tachenov May 03 '15 at 11:43
  • 1
    I'd add that `array` here should not be volatile, it should be private final. First that you need here is to make `writeData()` and `readData()` methods atomic or serializable. The simplest way is to declare them `synchronized` – Alex Salauyou May 03 '15 at 12:10
  • @SergeyTachenov then you also recommend use `AtomicInteger.incrementAndGet()` instead of `++`? – Shondeslitch May 03 '15 at 14:03
  • @Shondeslitch, what I recommend is to establish a synchronization policy that will ensure that any operations that modify a shared state do so in a thread-safe manner and preserve invariants atomically. How you implement this policy is another question. Perhaps using `AtomicInteger nElem` would do the trick (and `p` and `c` don't need to be volatile in this case), but if performance is not that important, I'd just synchronize the whole thing for the sake of simplicity and reliability. – Sergei Tachenov May 03 '15 at 15:55

1 Answers1

1

Your problem here is that your BufferCircular methods are sensitive to race conditions. Take for example writeData(). It executes in 3 steps, some of which are also not atomic:

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

Suppose that 2 threads entered writeData() at the same time. At step 1, they both have the same p value, and both rewrite array[p] value. Now, array[p] is rewritten twice and data that first thread had to write, is lost, because second thread wrote to the same index after. Then they execute step 2--and result is unpredictable since p can be incremented by 1 or 2 (p = (p + 1) % array.length consists of 3 operations, where threads can interact). Then, step 3. ++ operator is also not atomic: it uses 2 operations behind the scenes. So nElem becomes also incremented by 1 or 2.

So we have fully unpredictable result. Which leads to poor execution of your program.

The simplest solution is to make readData() and writeData() methods serialized. For this, declare them synchronized:

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

If you have only one producer and one consumer threads, race conditions may occur on operations involving nElem. Solution is to use AtomicInteger instead of int:

final AtomicInteger nElem = new AtomicInteger();

and use its incrementAndGet() and decrementAndGet() methods.

Alex Salauyou
  • 14,185
  • 5
  • 45
  • 67
  • But there is only one thread (Consumer) entered `writeData()`. Only one thread produce and another one consume. – Shondeslitch May 03 '15 at 12:31
  • Even if so, internal `nElem` variable is race-sensitive. `++` and `--` are not atomic. – Alex Salauyou May 03 '15 at 12:37
  • @Shondeslitch make `nElem` `AtomicInteger` instead of `int` and use its `incrementAndGet()` and `decrementAndGet` methods. – Alex Salauyou May 03 '15 at 12:41
  • 1
    Well, then `nElem++` is like `load nElem` + `inc 1` + `store nElem` and, if between these instructions we have a change of context, both threads can load not more recent value of nElem true? A lot of thanks, it works perfect. – Shondeslitch May 03 '15 at 13:59
  • 1
    @Shondeslitch, and even if there are only two threads accessing the whole thing, there are also visibility issues. So even if the operations were atomic, the other thread could just as well don't see at all what the first one is writing into the memory. Caching issues, optimization issues, JVM implementation details and so on... I seriously recommend reading the memory model part of Java Language Specification and a serious book on Java concurrency, such as Java Concurrency in Practice. – Sergei Tachenov May 03 '15 at 15:43
  • @Sergey plus one for Java Concurrency in Practice. Great book! – Alex Salauyou May 03 '15 at 15:59