Java多线程 线程安全之volatile

 

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等,可以很好的解决变量数据的正确性问题。

 

posted @ 2018-10-23 16:37  青山与妙高  阅读(507)  评论(0编辑  收藏  举报