云服务器

java中volatile关键字的浅析

2017-06-21 15:04:32 0

众所周知,java是一门支持多线程的编程语言。为了解决线程并发的问题,在语言内部引入了synchronized同步块和volatile关键字机制。

synchronized

synchronized这个大家都比较熟悉,也是平常项目中用的最多的线程同步方法。 通过在方法上或代码块中,添加此关键字,可以确保同一时刻只有一个线程能够访问里面的内容,从而实现线程同步的效果。

volatile

volatile关键字是用于修饰变量的。带有此关键字的变量,每次线程访问改变量时,都会读取到该变量最后修改的值。这个关键字,平时开发中自己用得比较少,但是在jdk和其他一些常用框架的源码里面,都可以很容易发现volatile的身影。

下面我们就一起来看看volatile的一些使用误区,以及它的原理。

首先,我们来看一段代码,下面是一个简单的计数器,每次线程启动的时候,就会调用inc方法,来对count属性加一。

``` public class Counter {

private static int count = 0;

public static void main(String[] args) {
    for(int i = 0; i < 1000; i++){
        new Thread(){
            @Override
            public void run() {
                inc();
            };
        }.start();
    }

    System.out.println("Count = " + count);
}

public static void inc(){
    try {
        Thread.sleep(10); // 增加计数耗时,使结果明显
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    count++;
}

} ``` 运行结果: Count = 970

每次的运行结果可能不一样,上面只是其中一次的结果。可以看出,在多线程的环境下,Counter.count并没有达到期望的1000。

可能有的朋友会想,这是一个多线程并发问题,只需要在变量count上加上volatile关键字,就可以实现线程同步,来避免这个问题。我们来尝试一下。

``` public class Counter {

private static volatile int count = 0;

public static void main(String[] args) {
    for(int i = 0; i < 1000; i++){
        new Thread(){
            @Override
            public void run() {
                inc();
            };
        }.start();
    }

    System.out.println("Count = " + count);
}

public static void inc(){
    try {
        Thread.sleep(10); // 增加计数耗时,使结果明显
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    count++;
}

} ``` 运行结果: Count = 888。

然并卵?下面我们来探讨一下原因。

jvm栈运行时内存分析

我们知道,在jvm运行的时候,其中有一个区域是分配给jvm虚拟机栈,每一个线程运行的时候,都会有一个线程栈。 线程栈是独立的,保存了线程运行时的变量信息。当线程访问某一个对象的属性值的时候,首先通过变量的引用,找到其在堆内存中的变量值,然后把堆内存的变量值load到线程本地内存中,建立一个副本,之后线程就不再和堆中变量值有任何关系,而是直接修改副本中的变量值,修改完之后的某一个时刻(线程退出前),会自动把线程副本的值写回对象在堆中的变量值。从而更新堆中对象属性值了。 详细过程可参考下图。

原因分析

对于之前Counter的例子,我们来分析一下。 通过前文的叙述,我们知道,volatile只是确保我们访问变量的时候,能够获取它最后更新的值。就是对应上面分析的read and load的过程,即线程从主内存读取变量值,并复制到线程栈副本的过程,获取到的值是最新的。

我们来看下面一种情况,假设线程1和线程2,并发读取主内存中的count变量,都为5。 那么线程1和线程2分别在线程栈中,对count变量进行加1,只是在他们对应的线程栈副本中,对count加1, 即为6, 并不影响主内存的count值。 当线程1和线程2结束后,把count变量写回主内存时,主内存中的值被修改为6。并不是预期的7。

所以,这就是出现这个问题的原因了。

总结

通过上文的分析,volatile相对于synchronized来说,只是能解决变量读的时候的线程安全问题,而不能解决并发写的线程安全问题。但是因为这样,性能上,也比synchronized要高效。我们需要根据合适的情况,来选择不同的线程同步方式。
 
上一篇: 无

微信关注

获取更多技术咨询