volatile修饰引用类型ArrayList的诡异问题
来源:13-1 volatile-

qq_凛冬将至_9
2022-02-01
老师新年好,在复习多线程这块时,看到一个程序的运行让人很疑惑,结合之前学的知识没法解释。程序如下:
package ...
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Test_00_monitor_list_to_five_issue {
// 1
volatile List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
Test_00_monitor_list_to_five_issue c = new Test_00_monitor_list_to_five_issue();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.err.println("add " + i); // 2
// sleepMilliSeconds(500); // 3
}
}, "t1").start();
new Thread(() -> {
while (true) {
if (c.size() == 5) {
break;
}
}
System.err.println("t2 结束");
}, "t2").start();
}
// 为了让调用处不用捕获异常,使得代码更简洁
public static void sleepMilliSeconds(int s) {
try {
TimeUnit.MILLISECONDS.sleep(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我试了下不同的处理方式:
- 1处不加
volatile
,不管2、3两处是否放开注释,t2线程都不会结束 - 1处加
volatile
,2、3放开一处的注释,t2都能正常结束 - 即使1处加了
volatile
,2、3两处都注释掉的情况下,t2线程也不能结束
令我疑惑的是,volatile
一般都修饰值类型,修饰引用类型,引用始终没改变,改变的只是内部的属性,在某些情况下(这里2、3放开一处的注释)其中的size字段可见了,这是什么原理?另外第三个操作volatile
为啥又失效了?2、3两处的代码对volatile
修饰的ArrayList中的size字段可见性有什么影响?
实在整懵了,向老师求救/(ㄒoㄒ)/~~
写回答
2回答
-
我在实验的时候,加了volatile,但是只放开2,不放开3,t2依然无法停止。
第3个处理方式,未必是volatile失效,也许是t1运行太快,t2还没判断的时候,t1就跑完10次循环了。
理论上说,其实2和3的代码,对size的可见性并没有影响,而是因为sleep的时候,Java会去尽量保证可见性,因此能看到size的变化。
012023-01-14 -
张婧仪
5天前
我来回答你的疑问
问题一,如果不加Volatile,没有可见性,所以t2不会停止
问题二三,加了Volatile,如果打开2,注释3,有时候可见,有时候不可见。如果注释2,打开3,一直可见。这是因为println和睡眠使得T1执行变慢,t2才有可能捕捉到,因为t2并不是一直在运行,由于CPU的调度。
=======
由于Volatile修饰的是List对象,而List包含size,size改变,list对象也改变,所以有可见性
00
相似问题