上文说到线程安全的两个特性,一是原子性,而是可见性,什么是可见性呢?
//不可见性
在多个线程操作同一个变量时,其它线程对一个线程的变量操作是不可见的,原因就涉及到JVM的内存模型了。
在介绍JVM内存模型时,我们经常提到内存为线程私有,所谓的线程私有,就是在线程运行时,JVM会为线程开辟一块专属于该线程的“工作内存”,用于存放栈内存,同时也将线程需要用到的主内存中的变量copy一份到工作内存中,当线程工作完后,再将变量写回主内存,在这个过程中,其它线程只能读取到主内存中的变量,所以说是不可见的。很显然的,synchronize关键字不仅实现了原子性,也实现了可见性,因为在解锁后,线程会把变量写回到主内存中,等待的线程会把写回主内存的变量再取出来,也就知道了上一个线程的操作,但是由于加锁的原因,会导致代码很复杂,效率会很低。
//可见性
volatile关键字的作用就是让线程之间可见,强制让线程将修改过的变量刷新至主内存中,另外,当主内存中变量修改,其它线程工作内存中的变量就会失效,且当再次读取该变量时,需要去主内存中重新读取变量的值。
所谓可见性,就是读操作时,对其他线程的写操作是可见的。
来看一段代码:
public class Demo extends Thread{
public void setIsrun(boolean isrun) {
this.isrun = isrun;
}
private boolean isrun = true;
public void run()
{
System.out.println("线程开始了!!!");
while(isrun==true)
{
}
System.out.println("线程结束了!!!");
}
public static void main(String args[]) throws InterruptedException {
Demo thead = new Demo();
thead.start();
Thread.sleep(3000);
thead.setIsrun(false);
System.out.println(thead.isrun);
}
}
没有对变量加volatile关键字修饰下,处理这么一个逻辑:写一个线程,当isrun为true的时候运行(isrun初始值为true),在主线程中先运行该线程,然后再修改isrun变量为false。启动就会发现,线程无法停下,但是isrun已经修改过了(当JVM未被优化的时候,在cmd中输入java -version,会显示jvm为客户端,此时线程会停下,私以为,因为启用“工作内存”是JDK1.5以后的版本,若未优化,则还是会在主内存中读写,看不出效果,资料来源于:传送门)。
然后再将isrun改为volatile修饰过的,此时线程才会停下来,volatile使得该变量可见。
重点:volatile仅仅只能提供多个线程之间的可见性,但是不会让线程具有原子性,要实现线程的原子性,还是得靠synchronize关键字,volatile对synchronize不具有替代作用。
因为,即使volatile是变量在线程间可见,但是他的任何操作还是分为多步骤,如i++,还是先取出i,然后++,最后写回,若有其他线程在第一步取出阶段便读取了数据,也写回,但是晚于前一个线程,由于这个过程没有再次进行读的操作,所以是不会刷新工作内存的i的,导致明明是加了两次的操作,却只得到加了一次的结果,这时是线程不安全的。
volatile相对于synchronize,性能更加的好,netty底层便是使用大量volatile进行编写,可见其性能。虽然无法替代synchronize,但是在大多数可见性的业务上,可以使用volatile关键字,如状态标志变量。