JVM 似乎很快就停止了上下文切换
JVM seems to stop context switching very quickly
我正在实现生产者-消费者并发问题的原始版本。并且线程之间的切换起初非常快,但随后停止在 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);
}
}
你的问题没有提供足够的细节来给出自信的答案(至少,不清楚那些额外的打印语句去哪里了),所以我会在这里做一些(合理的)猜测。
您的代码不正确。 IntBuffer 不是线程安全的,但它是从多个线程访问的。
对 IntBuffer 的任何操作都不会建立 happens-before 关系,因此一个线程所做的更改可能对另一个线程不可见。这就是为什么生产者线程可以 "believe" 缓冲区已满而消费者线程 "believes" 缓冲区为空的原因。在这种情况下,程序永远不会终止。
这两个说法不是猜测,是基于Java内存模型的事实。我猜想为什么额外的打印语句可以修复它:
- 在许多 JVM 实现中,println 方法在内部使用同步。这就是为什么调用它会创建内存栅栏并使一个线程中的更改对另一个线程可见,从而消除 2).
中描述的问题。
但是,如果你真的想解决这个问题,你应该使 IntBuffer 线程安全。
您至少需要在 buffer
和 index
上使用 volatile
关键字。其次,您只需要在 ifs 的真实臂下访问 index
一次。即使在那之后,您将在 10 点面临越界访问,您将需要更多修复来解决这个问题。您的缓冲区实际上是堆栈。因此,即使在所有这些之后,您的 remove()
仍可能使用陈旧索引,因此您将在堆栈中间删除。您可以使用 0 作为特殊值,标记已处理的槽结束为空。
综上所述,我认为您的代码不容易修复。它几乎需要使用适当的工具完全重写。我同意@kraskevich:
@StuartHa Naive usually means simple(and most likely inefficent) solution, not an incorrect one.
我正在实现生产者-消费者并发问题的原始版本。并且线程之间的切换起初非常快,但随后停止在 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);
}
}
你的问题没有提供足够的细节来给出自信的答案(至少,不清楚那些额外的打印语句去哪里了),所以我会在这里做一些(合理的)猜测。
您的代码不正确。 IntBuffer 不是线程安全的,但它是从多个线程访问的。
对 IntBuffer 的任何操作都不会建立 happens-before 关系,因此一个线程所做的更改可能对另一个线程不可见。这就是为什么生产者线程可以 "believe" 缓冲区已满而消费者线程 "believes" 缓冲区为空的原因。在这种情况下,程序永远不会终止。
这两个说法不是猜测,是基于Java内存模型的事实。我猜想为什么额外的打印语句可以修复它:
- 在许多 JVM 实现中,println 方法在内部使用同步。这就是为什么调用它会创建内存栅栏并使一个线程中的更改对另一个线程可见,从而消除 2). 中描述的问题。
但是,如果你真的想解决这个问题,你应该使 IntBuffer 线程安全。
您至少需要在 buffer
和 index
上使用 volatile
关键字。其次,您只需要在 ifs 的真实臂下访问 index
一次。即使在那之后,您将在 10 点面临越界访问,您将需要更多修复来解决这个问题。您的缓冲区实际上是堆栈。因此,即使在所有这些之后,您的 remove()
仍可能使用陈旧索引,因此您将在堆栈中间删除。您可以使用 0 作为特殊值,标记已处理的槽结束为空。
综上所述,我认为您的代码不容易修复。它几乎需要使用适当的工具完全重写。我同意@kraskevich:
@StuartHa Naive usually means simple(and most likely inefficent) solution, not an incorrect one.