直接先举一个例子普通的线程实例变量的非可见性:
public class MyThread28 extends Thread { private boolean isRunning = true; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } public void run() { System.out.println("进入run了"); while (isRunning == true){} System.out.println("线程被停止了"); } }
public static void main(String[] args) { try { MyThread28 mt = new MyThread28(); mt.start(); Thread.sleep(1000); mt.setRunning(false); System.out.println("已赋值为false"); } catch (InterruptedException e) { e.printStackTrace(); } }
看一下运行结果:
进入run了 已赋值为false
也许这个结果有点奇怪,明明isRunning已经设置为false了, 线程还没停止呢?
这就要从Java内存模型(JMM)说起,这里先简单讲,虚拟机那块会详细讲的。根据JMM,Java中有一块主内存,不同的线程有自己的工作内存,同一个变量值在主内存中有一份,如果线程用到了这个变量的话,自己的工作内存中有一份一模一样的拷贝。每次进入线程从主内存中拿到变量值,每次执行完线程将变量从工作内存同步回主内存中。
注意这里的情况只会出现Server模式,Client模式不会出现,而且要先睡一段时间再设置false,估计是为了让子线程read load 好主内存字段吧,不设置睡觉的话,子线程来不及 read load 就会立刻停止了
出现打印结果现象的原因就是主内存和工作内存中数据的不同步造成的。因为执行run()方法的时候拿到一个主内存isRunning的拷贝,而设置isRunning是在main函数中做的,换句话说 ,设置的isRunning设置的是主内存中的isRunning,更新了主内存的isRunning,线程工作内存中的isRunning没有更新,当然一直死循环了,因为对于线程来说,它的isRunning依然是true。
解决这个问题很简单,给isRunning关键字加上volatile。加上了volatile的意思是,每次读取isRunning的值的时候,都先从主内存中把isRunning同步到线程的工作内存中,再当前时刻最新的isRunning。看一下给isRunning加了volatile关键字的运行效果:
进入run了 已赋值为false 线程被停止了
看到这下线程停止了,因为从主内存中读取了最新的isRunning值,线程工作内存中的isRunning变成了false,自然while循环就结束了。
volatile之前的普通线程内存模型
使用 volatile 之后
这里可以参考 java虚拟机9:Java的内存模型
volatile的作用就是这样,被volatile修饰的变量,保证了每次读取到的都是最新的那个值。线程安全围绕的是可见性和原子性这两个特性展开的,volatile解决的是变量在多个线程之间的可见性,但是无法保证原子性。
多提一句,synchronized除了保障了原子性外,其实也保障了可见性。因为synchronized无论是同步的方法还是同步的代码块,都会先把主内存的数据拷贝到工作内存中(synchronized 只要一经线程调用,无所谓你锁的谁,只要跟进程相关的实例变量,静态变量都会同步到主内存,实现可见性,下面可以看到例子),同步代码块结束,会把工作内存中的数据更新到主内存中,这样主内存中的数据一定是最新的。
volatile非原子特性
运行结果
证明了非原子性,但是保证是可见性的
下面更改代码 使用 synchronized 代替 volatile
运行结果
使用 synchronized 就没必要使用 volatile 了
synchronized拥有volatile的同步功能
可以看的出来 ·synchronized 只要一经线程调用,无所谓你锁的谁,只要跟进程相关的实例变量,静态变量都会同步到主内存,实现可见性