为何仅创建一个对象,Thread却检测到两个实例?
代码示例
public class ThreadExample { public static ArrayList<ThreadExample> instances = new ArrayList<ThreadExample>(); public ThreadExample() { instances.add(this); } public static void main(String[] args) { ThreadRead threadRead = new ThreadRead(); ThreadCreate threadCreate = new ThreadCreate(); threadCreate.start(); threadRead.start(); } } class ThreadRead extends Thread { public void run() { System.out.println(ThreadExample.instances.size()); } } class ThreadCreate extends Thread { public void run() { ThreadExample.instances.add(new ThreadExample()); } }
问题描述
该代码运行输出为"2",但预期输出应为1或0:若ThreadCreate线程创建对象时ThreadRead线程同时读取,可能因对象构造未完成读到0;若执行正常则读到1。想请教其中未知的原因是什么?
原因解析
你忽略了一个关键细节:ThreadExample的构造函数里已经调用了instances.add(this),而ThreadCreate的run方法中又额外执行了一次ThreadExample.instances.add(new ThreadExample())。
当ThreadCreate线程执行new ThreadExample()时,会先触发构造函数,此时instances会添加当前实例(size变为1);构造函数执行完成后,run方法里的add语句会再次把刚刚创建的实例添加到instances中(size变为2)。这两个操作在同一个线程内是顺序执行的,不存在线程安全问题,所以只要ThreadCreate线程的run方法执行完毕,instances的大小就一定是2。
如果ThreadRead线程在ThreadCreate线程完成这两次add操作之后才读取,就会输出2;如果ThreadRead在ThreadCreate开始执行前就读取,会输出0;但由于线程调度的不确定性,输出2是完全合理的结果——因为ThreadCreate的两次add是原子性的(同一线程内顺序执行),不会出现读到1的情况。
另外需要补充:ArrayList本身不是线程安全的集合,即使没有构造函数里的额外add,多线程下读写也可能出现不一致,但这个案例里的核心问题是重复添加的逻辑错误,而非线程安全问题。
内容的提问来源于stack exchange,提问作者MUSA BEYTEKİN

