我有这个程序:
Farmer.java:
import java.util.*;
public class Farmer implements Runnable {
List<Apple> busket;
private static int count;
private int id;
private int appleCount;
public Farmer(List<Apple> busket) {
this.busket = busket;
id = count++;
}
public void makeApple() {
Apple apple = new Apple("Apple" + appleCount + " by Farmer"+id);
System.out.println("making apple: " + apple);
busket.add(apple);
appleCount++;
}
public void run() {
while (appleCount<5) {
makeApple();
}
}
}
Apple.java:
public class Apple {
private String name;
public String getName() {return name;}
public String toString() {return name;}
public Apple(String name) {
this.name = name;
}
}
主.java:
import java.util.*;
class Main {
public static void main(String[] args) throws Exception {
System.out.println("Hello world!");
List<Apple> apples = new ArrayList<>();
Thread t1 = new Thread(new Farmer(apples));
t1.start();
Thread t2 = new Thread(new Farmer(apples));
t2.start();
while (apples.size()==0) {
// System.out.println(apples.size());
}
System.out.println(apples.size());
}
}
它给出了这个输出:
Hello world!
making apple: Apple0 by Farmer1
making apple: Apple1 by Farmer1
making apple: Apple2 by Farmer1
making apple: Apple3 by Farmer1
making apple: Apple4 by Farmer1
making apple: Apple0 by Farmer0
making apple: Apple1 by Farmer0
making apple: Apple2 by Farmer0
making apple: Apple3 by Farmer0
making apple: Apple4 by Farmer0
所以它永远不会打印
System.out.println(apples.size());
但是如果我取消注释
// System.out.println(apples.size());
在 while 循环中
我得到这个输出:
...many more zeroes
0
0
0
making apple: Apple0 by Farmer1
0
0
...a bit more zeroes
1
making apple: Apple0 by Farmer0
making apple: Apple1 by Farmer0
making apple: Apple2 by Farmer0
making apple: Apple3 by Farmer0
making apple: Apple4 by Farmer0
making apple: Apple1 by Farmer1
making apple: Apple2 by Farmer1
making apple: Apple3 by Farmer1
making apple: Apple4 by Farmer1
你可以看到
1
在上面的输出中,就是数组的大小。
而且程序似乎永远不会停止。
我也试过这个循环:
int i=0;
while (apples.size()==0) {
System.out.println("i: " + i); //works as previous: if uncommented, there's size output later, if commented out, no size output, program keeps running
}
和
int i=0;
while (apples.size()==0) {
//System.out.println("i: " + i);
i++; //doesn't influence - no size output unless uncomment system.out.println
}
它有相同的行为
为什么会出现这种行为?为什么在 while 循环中添加 print 语句会给出正确的输出(数组的大小和程序的结束),而删除它似乎会使程序卡在 while 循环中,即使数组已填满?
编辑:
我知道使用同步列表可以解决这个问题。我想了解为什么会这样,而不是修复代码。
最佳答案
快速 TL;DR 解决方案:插入一些 synchronized
语句来保护对共享列表的访问!
Java 不保证当多个线程访问同一个变量时没有显式同步时事情会正常工作。
如果你有
while (apples.size()==0) {
// System.out.println(apples.size());
}
允许 JIT
内联
apples.size()
的主体,它很可能只是读取列表对象中的私有(private)变量,并且然后发出代码,只从堆中读取变量一次,并一劳永逸地测试它存储在寄存器中的大小副本。其他线程所做的任何事情都不会改变这个本地缓存的大小信息副本。因此,因为列表的长度开始为 0,所以这就是测试将永远看到的 -- 你有一个无限循环。
JIT 被允许这样做是因为 Java 的并发模型表明一个线程不能保证能够看到另一个线程对堆字段所做的更改,直到两个线程都通过了一些适当的同步操作。
当您插入对 System.out.println()
的调用时,循环体现在变得如此复杂以至于 JIT 无法证明 System.out.println ()
不会以某种方式 更改列表对象中的内容。 (我们 可以很容易地看出它不会,但 JIT 是一只没有头脑的熊,因此不能)。因此它不敢共享它在循环的前一次迭代中读取的大小字段的值;相反,它会创建机器代码,每次循环都会从堆对象中读取一次大小字段。因此——但绝对是意外多于设计! -- 您的主线程将最终从其他线程获取更改。
这并不意味着现在一切都很好,因为您仍然有两个不同的线程试图修改 ArrayList
并且没有任何东西可以防止它们踩到彼此的脚趾。那时可能会出现各种奇怪的事情。它可能只是恰好按照您的预期工作。或者,如果您真的很幸运,您可能会在其中一个线程中抛出一个很好的显式 ConcurrentModificationException
。或者一个或多个苹果可能会悄无声息地丢失,或者您可能会遇到 NullPointerException
或更糟的情况,如果您稍后尝试再次将苹果 从列表中取出。
关于java - 两个线程将对象添加到 ArrayList : can't output the size: Why does removing println lead to an infinite loop,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57333778/