我正在实现生产者 - 消费者并发问题的天真版本。并且线程首先在非常快速之间切换,然后在i = 50
周围停止。由于某种原因添加其他打印语句允许JVM上下文切换线程并完成程序。
为什么JVM上下文不切换线程以便程序完成?
// 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);
}
}
答案 0 :(得分:4)
您的问题没有提供足够的详细信息来给出答案的信心(至少,这些额外的印刷语句去哪里都不清楚),所以我会在这里做一些(合理的)猜测。
您的代码不正确。 IntBuffer不是线程安全的,但可以从多个线程访问它。
IntBuffer上的任何操作都不会建立before-before关系,因此一个线程所做的更改可能对另一个线程不可见。这就是为什么Producer线程可以“相信”缓冲区已满,而Consumer线程“认为”它是空的。在这种情况下,程序永远不会终止。
这两个陈述不是猜测,它们是基于Java内存模型的事实。这就是我的猜测为什么附加的打印语句可以解决它的问题:
但是,如果你真的想解决这个问题,你应该让IntBuffer线程安全。
答案 1 :(得分:1)
至少您需要volatile
和buffer
上的index
关键字。其次,你需要在你拥有的ifs的真正手臂下访问index
一次。即使在那之后,你将面临10点的界限访问,你将需要更多的修复来解决这个问题。你的缓冲区是事实上的堆栈。因此,即使在所有这些之后,您的remove()
也可以使用陈旧索引,因此您将在堆栈中间删除。您可以使用0作为特殊值,标记已经处理的插槽为空。
有了这一切,我认为您的代码不易被挽救。它几乎需要使用适当的设施进行完全重写。我同意@kraskevich:
@StuartHa Naive usually means simple(and most likely inefficent) solution, not an incorrect one.