用代码描述这么一个场景,在main方法中开启两个线程,其中一个线程t1往list里循环添加元素,另一个线程t2监听list中的size,当size等于5时,t2线程结束,t1线程继续执行,直到循环结束,试着想一想,以下代码能不能实现:
1 package com.fanjf.thread; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class Mycontainer { 7 static List<Integer> integers = new ArrayList<>(); 8 9 public static void main(String[] args) { 10 new Thread(new Runnable() { 11 public void run() { 12 System.out.println("t1启动"); 13 for(int i=0;i<10;i++){ 14 integers.add(i); 15 try { 16 Thread.sleep(1000); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 System.out.println("add:"+i); 21 } 22 System.out.println("t1结束"); 23 } 24 }).start(); 25 26 new Thread(new Runnable() { 27 public void run() { 28 System.out.println("t2启动"); 29 while(true){ 30 if(integers.size()==5){ 31 break; 32 } 33 } 34 35 System.out.println("t2结束"); 36 } 37 38 }).start(); 39 } 40 41 }
输出结果如下,且程序并没有结束,一直处于运行状态
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规则。
原文:https://www.cnblogs.com/fanjianfei/p/10712235.html