用代码描述这么一个场景,在main方法中开启两个线程,其中一个线程t1往list里循环添加元素,另一个线程t2监听list中的size,当size等于5时,t2线程结束,t1线程继续执行,直到循环结束,试着想一想,以下代码能不能实现:
package com.fanjf.thread; import java.util.ArrayList;
import java.util.List; public class Mycontainer {
static List<Integer> integers = new ArrayList<>(); public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
System.out.println("t1启动");
for(int i=0;i<10;i++){
integers.add(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("add:"+i);
}
System.out.println("t1结束");
}
}).start(); new Thread(new Runnable() {
public void run() {
System.out.println("t2启动");
while(true){
if(integers.size()==5){
break;
}
} System.out.println("t2结束");
} }).start();
} }
答案是不行,输出结果如下,且程序并没有结束,一直处于运行状态
t1启动
t2启动
add:0
add:1
add:2
add:3
add:4
add:5
add:6
add:7
add:8
add:9
t1结束
有并发编程经验的人应该很容易就能看出问题所在,这里涉及到了线程之间的可见性问题,只需要在上面第7行的integers变量前加上volatile便可以得到想要的结果,如下红色部分:
volatile static List<Integer> integers = new ArrayList<>();
输出结果如下:
t1启动
t2启动
add:0
add:1
add:2
add:3
t2结束
add:4
add:5
add:6
add:7
add:8
add:9
t1结束
这里的volatile作用是为了保证线程之间的可见性,java之间线程之间通信是通过共享内存来实现的,当t1线程向integers中add元素时,如果不加volatile,这个size的变化t2线程是无法感知的,因为当线程执行时,t2会在执行当前线程的cpu中缓存一份integers,t2会优先读取自己线程本地变量的integers,而不从主内存中读取,也就是两个线程之间是不可见的,当在这个共享变量加上volatile时,t1向integers中add元素,即修改了共享变量integers的值,程序会通知t2重新从主内存中读取共享变量的值,这时候t1和t2之间就是可见的,这其实是happens before规则中其中重要的一条:volatile的写happens before volatile的读,意思就是说一个线程对volatile修饰的变量的写操作对于其他线程来说一定是可见的,感兴趣的可以自己了解一下happens before规则。