Java并发之volatile关键字

该文章发布于 ,归类于 Java

这牵扯到了原子性的问题,所谓原子性,即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

比如如下的赋值语句

String name = "leevare";

name赋值为leevare,这种操作可看做是不可被中断的,那么就是一个原子操作。

那么对于会发生中断的操作会出现什么问题呢?这种问题在多线程中尤为常见。

举个例子,在银行转账,当A用户转账给B用户1000元时,必定会发生这样两个操作,首先,从A的账户上减去1000元,然后,在B的账户上加上1000元。看似这个操作平淡无奇,假如,A本来账户上拥有本金2000元,在A用户转账给B用户1000元后,程序还没有来得及在A账户上减去1000,此时,如果有另外一个用户C在A的账户上来进行取款操作,查询发现余额也是2000元,然后取出2000,在此同时,用户A在转账时查询的结果也是2000,然后进行转账后,系统再减去1000,很明显,这出现了很严重的问题。

出现上面的问题,就是因为两个线程之间对数据操作都是不可见的。线程2在进行扣款时,线程1并不知道发生了扣款操作,这就导致了数据出现了错误。

对于多核CPU而言,每一条线程都有一块自己独占的高速缓存,在执行时,会将执行信息收到读取到自己的高速缓存中。上面的问题就可以看做是这样一种情况,线程一和线程二都从主线程读取了必要信息到自己的高速缓存中,线程一对数据进行了修改,还没有来得及写入主线程,线程二也对其进行了修改,然后再写入主线程,就出现了数据操作的错误。

可以通过使用synchronized关键字来加锁,将数据操作的方法锁定,让线程保持同步,这时,只能有一个线程才能进行操作,这样,就能解决了多线程间数据不一致的问题。但是,在锁定期间,其余的也有操作该方法的线程都会处于阻塞状态,对于执行效率方面会带来一定的影响。

使用volatile关键字修饰的变量,表示该变量在多线程操作之间是透明可见的,修改之后,其余的线程也能立即感知,就解决了数据变化的问题。

为什么是透明可见呢?

使用volatile修饰后,会强制将修改的值立即写入主存。同样是上面的问题,两个线程同时读取了必要的信息到自己的高速缓存中,线程一如果对数据(比如说金额是count)进行了修改后,立即将count写入到主线程,会导致线程二中原来读取的count立即失效,这时候线程二会重新从主线程中读取数据,此时,线程二就会读取到正确的数据,从而保证了数据的同步。

例如如下的例子

public class TestMain {

    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        new Thread(testThread, "t1").start();
        new Thread(testThread, "t2").start();
    }
}

class TestThread implements Runnable {

    private volatile boolean stop = false;

    public void run() {
        if ("t1".equals(Thread.currentThread().getName())) {
            while (!stop) {
                System.out.println(111);
            }
        } else if ("t2".equals(Thread.currentThread().getName())) {
            stop = true;
        }
    }
}

这里会输出111一次,是因为第二个线程修改了stop之后,第一个线程也能立即感知,就不会出现死循环的情况。反之,如果将volatile关键字去除后,就会出现死循环了。

相关文章