Java 向ArrayList添加对象的两个线程:can';t输出大小:为什么删除println会导致无限循环
我有这个计划: Farmer.java:Java 向ArrayList添加对象的两个线程:can';t输出大小:为什么删除println会导致无限循环,java,concurrency,Java,Concurrency,我有这个计划: 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 = b
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();
}
}
}
public class Apple {
private String name;
public String getName() {return name;}
public String toString() {return name;}
public Apple(String name) {
this.name = name;
}
}
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());
}
}
Main.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();
}
}
}
public class Apple {
private String name;
public String getName() {return name;}
public String toString() {return name;}
public Apple(String name) {
this.name = name;
}
}
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());
}
}
所以它从不打印
System.out.println(apples.size());
但是如果我取消注释
// System.out.println(apples.size());
内部while循环
我得到这个输出:
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
...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
}
及
它也有同样的行为
为什么会有这种行为?为什么在while循环中添加print语句会给出正确的输出(数组的大小和程序的完成),而删除它似乎会使程序卡在while循环中,即使数组被填满
编辑:
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();
}
}
}
public class Apple {
private String name;
public String getName() {return name;}
public String toString() {return name;}
public Apple(String name) {
this.name = name;
}
}
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());
}
}
我知道使用同步列表可以解决这个问题。我想了解为什么这样做,而不是修复代码。将ArrayList创建包装到一个同步对象将起到作用,因为原始ArrayList实现不是线程安全的,或者您可以使用Vector
import java.util.*;
class Main {
public static void main(String[] args) throws Exception {
System.out.println("Hello world!");
List<Apple> apples = Collections.synchronizedList(new ArrayList<>());
//Or
//List<Apple> apples = new Vector<>();
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());
}
}
import java.util.*;
班长{
公共静态void main(字符串[]args)引发异常{
System.out.println(“你好,世界!”);
List apples=Collections.synchronizedList(新的ArrayList());
//或
//列表=新向量();
线程t1=新线程(新农民(苹果));
t1.start();
线程t2=新线程(新农民(苹果));
t2.start();
而(apples.size()==0){
//System.out.println(apples.size());
}
System.out.println(apples.size());
}
}
将ArrayList创建包装到一个同步对象将实现这一点,因为原始ArrayList实现不是线程安全的,或者您可以使用Vector
import java.util.*;
class Main {
public static void main(String[] args) throws Exception {
System.out.println("Hello world!");
List<Apple> apples = Collections.synchronizedList(new ArrayList<>());
//Or
//List<Apple> apples = new Vector<>();
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());
}
}
import java.util.*;
班长{
公共静态void main(字符串[]args)引发异常{
System.out.println(“你好,世界!”);
List apples=Collections.synchronizedList(新的ArrayList());
//或
//列表=新向量();
线程t1=新线程(新农民(苹果));
t1.start();
线程t2=新线程(新农民(苹果));
t2.start();
而(apples.size()==0){
//System.out.println(apples.size());
}
System.out.println(apples.size());
}
}
快速TL;灾难恢复解决方案:插入一些synchronized
语句以保护对共享列表的访问
当多个线程在没有显式同步的情况下访问同一个变量时,Java不能保证事情能够正常工作 如果你有
while (apples.size()==0) {
// System.out.println(apples.size());
}
JIT被允许
apples.size()
的主体,它很可能只读取列表对象中的一个私有变量,然后System.out.println()
的调用时,发生的情况是循环体现在非常复杂,JIT无法证明System.out.println()
不会以某种方式更改列表对象内部的内容。(我们可以很容易地看到,它不会,但JIT是一个大脑非常小的熊,不能)。因此,它不敢共享在循环的上一次迭代中读取的size字段的值;相反,它创建机器代码,在循环中每次从heap对象读取一次size字段。因此——但肯定更多的是偶然而非设计您的主线程最终将从其他线程获取更改
这并不意味着现在一切都很好,因为仍然有两个不同的线程试图修改
ArrayList
,并且没有任何东西可以阻止它们互相攻击。所有奇怪的事情都可能出差错。它可能恰好按照您的预期工作。或者,如果您真的很幸运,您可能会在其中一个线程中抛出一个漂亮的显式ConcurrentModificationException
。或者一个或多个苹果可能会自动丢失,或者如果以后再次尝试将苹果从列表中删除,您可能会遇到NullPointerException
或更糟的情况。Quick TL;灾难恢复解决方案:插入一些synchronized
语句以保护对共享列表的访问
当多个线程在没有显式同步的情况下访问同一个变量时,Java不能保证事情能够正常工作 如果你有
while (apples.size()==0) {
// System.out.println(apples.size());
}
JIT被允许
apples.size()
的主体,它很可能只读取列表对象中的一个私有变量,然后System.out.println()
的调用时,发生的情况是循环体现在非常复杂,JIT无法证明System.out