countunique
countunique

Reputation: 4296

JVM seems to stop context switching very quickly

I'm implementing the naive version of the Producer-Consumer concurrency problem. And it the threads are switched between at first very quickly but then stop around i = 50. Adding additional print statements for some reason allows the JVM to context switch the threads and complete the program.

Why doesn't the JVM context switch the threads so that the program will complete?

// Producer - Consumer problem
// Producer constantly puts items into array, while consumer takes them out

class IntBuffer {
    private int[] buffer;
    private int index;

    public IntBuffer(int size) {
        buffer = new int[size];
        index = 0;
    }

    public void add(int item) {
        while (true) {
            if (index < buffer.length) {
                buffer[index] = item;
                index++;
                return;
            }
        }
    }

    public int remove() {
        while (true) {
            if (index > 0) {
                index--;
                int tmp = buffer[index];
                buffer[index] = 0;
                return tmp;
            }
        }
    }

    public void printState() {
        System.out.println("Index " + index);
        System.out.println("State " + this);
    }

    public String toString() {
        String res = "";
        for (int i = 0; i < buffer.length; i++) {
            res += buffer[i] + " ";
        }
        return res;
    }
}

class Producer extends Thread {
    private IntBuffer buffer;

    public Producer(IntBuffer buffer) {
        this.buffer = buffer;
    }

    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("added " + i);
            buffer.add(i);
        }
    }
}

class Consumer extends Thread {
    private IntBuffer buffer;

    public Consumer(IntBuffer buffer) {
        this.buffer = buffer;
    }

    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("removed " + i);
            buffer.remove();
        }
    }
}

public class Main {

    public static void main(String[] args) {
        IntBuffer buf = new IntBuffer(10);
        Thread t1 = new Thread(new Producer(buf));
        Thread t2 = new Thread(new Consumer(buf));
        t1.start();
        t2.start();
        System.out.println(buf);
    }
}

Upvotes: 0

Views: 102

Answers (2)

wilx
wilx

Reputation: 18238

At the minimum you need the volatile keyword on both the buffer and index. Second, you need to access index only once under the true arm of the ifs you have there. Even after that, you will face out of bounds access at 10, you will need more fixing to work around that. Your buffer is de facto stack. So, even after all of this, your remove() can be working with stale index, thus you will be removing in the middle of the stack. You could use 0 as special value marking the slot already handled end empty.

With all of this, I do not think your code is easily salvageable. It pretty much needs complete rewrite using proper facilities. I agree with @kraskevich:

@StuartHa Naive usually means simple(and most likely inefficent) solution, not an incorrect one.

Upvotes: 1

kraskevich
kraskevich

Reputation: 18566

Your question does not provide enough details to give an answer with a confidence(at least, it is not clear where those additional print statements go), so I'll make some(reasonable) guesses here.

  1. Your code is not correct. IntBuffer is not thread-safe, but it is accessed from multiple threads.

  2. Any operations on the IntBuffer do not establish a happens-before relationship, so the changes made by one thread may be not visible for another thread. That's why the Producer thread can "believe" that the buffer is full while the Consumer thread "believes" that it is empty. In this case the program never terminates.

This two statements are not guesses, they are facts based on the Java memory model. And here goes my guess why the additional print statements sorta fix it:

  1. In many JVM implementations, the println methods uses syncronization internally. That's why a call to it creates a memory fence and makes changes made in one thread visible to the other one, eliminating the issue described in 2).

However, if you really want to solve this problem, you should make the IntBuffer thread-safe.

Upvotes: 4

Related Questions