Java线程安全主要是由两个特性来组成,1、原子性。2、可见性。
volatile关键字
原子性类似于数据库事务中的原子性,一个操作必须有始有终,不能中途被停止。
而可见性的意思:是多个线程之间访问共享变量时,A线程所修改的变量需要及时的被B线程或其他所有线程所读取到。
直接上代码,下面先看看当一个典型的多线程同步时的问题。
public class RunThread implements Runnable { private boolean isRunning = true; public boolean isRunning() { return isRunning; } public void setRunning(boolean isRunning) { this.isRunning = isRunning; } @Override public void run() { System.out.println("进入run了"); while (isRunning) { } System.out.println("线程被停止了!"); } public static void main(String[] args) throws InterruptedException { RunThread thread = new RunThread(); new Thread(thread).start(); Thread.sleep(100); thread.setRunning(false); System.out.println("已经赋值为false"); }}
输出结果:
根据代码,我们在main线程中已经设置isRunning为false,子线程thread应该跳出while的死循环,打印出“线程被停止了!”,而结果为什么没有呢?
这就引出了JMM 内存模型的主存和内存的关系问题,先看下JMM 内存模型的示意图。
可以看到,每个线程都拥有一个独立的本地内存(也可以叫工作内存),而下面大的主内存则是由所有线程所共享的,其实每次线程中所读取到的变量都是从主存中得来的,然后写入到自己的工作内存,然后进行操作。
基于这个模型,我们其实就可以回答上面的问题,在子线程中我们每次读取isRunning,都是从本地内存中,所有取得值一直是false,而在主线程中我们设置了isRunning为true是在主存中,子线程中没有及时的读取到主线程中改变的isRunning变量。这就造成了一个线程之间共享变量没有及时同步,也可以说是一个线程改变的值,没有及时被其他线程所看到。这里我们就引入了volatile关键字,只要被它修饰的变量在进行修改时,会向总线(BUS)发送消息,其他线程读取到该变量时,会得到最新的值。简单的来说,如果想要某个变量被所有线程读取到最新的值,就使用volatile关键字进行修饰。
所以在上面,我们仅仅需要在isRunning前面加上volatile关键字修饰,如下:
private volatile boolean isRunning = true;
输出结果:
可以看到,程序已经如我们所预期的,达到线程安全的效果了。
volatile的局限性
其实volatile关键字解决的仅仅是线程之间可见性的问题,换句话说,它仅仅的解决的是线程之间数据能否被读取到,而没有解决多线程的原子性问题,即读到的数据是不是一个线程完整执行后的结果值。如果想保证多线程的原子性问题,可以参考java下的concurrent包下提供了一些原子类,如tomicInteger、AtomicLong、AtomicReference等,可以很好的解决变量数据的正确性问题。