volatile关键字

最近在看Java内存模型时,看到了volatile关键字,现将该关键字的用法总结一下。volatile变量主要有两方面的特性:一是保证了此volatile变量对所有线程的可见性,二是volatile变量禁止指令重排序优化。

在解释volatile变量的第一个特性前,先来解释可见性(Visibility)这个概念。可见性是指当一个线程修改了共享变量的值,其他线程能够立即得知这个改变。我们知道处理器与主内存之间一般都有高速缓存(Cache),这是因为处理的运算速度和内存的读取速度相差几个数量级。同样,在Java内存模型中,有相应的工作内存和主内存,可以把它们与高速缓存和主内存相对应。Java线程需要共享的变量存储在主内存中,每个Java线程都有自己的工作内存。线程、主内存、工作内存三者的关系如下图所示:


线程、工作内存、主内存三者的交互关系

在使用volatile变量前,都要从主内存内读取最新的值,用于保证能看见其他线程对volatile变量所做修改后的值。在每次修改volatile变量后都必须立刻同步回主内存中,用于保证其它线程可以看到自己对变量所做的修改。这是volatile变量的特殊规则,也是volatile变量可见性的由来。

无论是普通变量还是volatile变量都是通过主内存作为传递媒介的方式来实现可见性。普通变量与volatile变量的区别在于volatile变量的特殊规则保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。因此我们可以说volatile保证了多线程操作时变量的可见性,而普通变量则不能保证这一点。但是应该注意的是 volatile变量的可见性并不能保证其运算在并发下是安全的。这是因为如果Java里面的运算并非原子操作,则volatile变量的运算在并发情况下是不安全的。例如:

public class VolatileTest{
    public static volatile int race = 0;
    public static void increase(){
        race++;
    }

    private static final int THREADS_COUNT = 20;
    public static void main(String[] args){
        Thread[] threads = new Thread[THREADS_COUNT];
        for(int i = 0; i < THREADS_COUNT; i++){
            threads[i] = new Thread(new Runnable(){
                @Override
                public void run(){
                    for(int i = 0; i < 10000; i++){
                        increase();
                    }
                }
            });
            threads[i].start();
        }
        //等待所有累加线程都结束
        while(Thread.activeCount() > 1)
            Thread.yield();//线程主动让出执行时间
        System.out.println(race);
    }
}
这段代码执行完的结果将小于20000,并且每次执行的结果都是不相同的。如果这段代码能够正确并行执行的话,那么这段代码的执行结果应该为20000。出现这样结果的原因是自增运算“race++”并非原子操作,这样当对race操作时,其它线程可能已经对race值进行了更改。volatile关键字只能保证此时读到的值是正确的,但在执行完其他运算操作,其它线程可能已经把race的值增大了,这样就有可能把较小的race值同步回主内存之中,所以所有线程累加结果将小于20000。

volatile变量只能保证可见性,并不能保证并发的安全。除了下面两种情况不需要加锁来保证原子性,其余的volatile变量运算仍然需要通过加锁来保证并发下的安全。

  • 运算结果并不依赖变量的当前值,或着能够确保只有一个线程来修改变量的值;
  • 变量不需要与其他的状态变量共同参与不变约束;
volatile变量的另一个特性是volatile变量禁止指令重排序优化。普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量的赋值操作顺序与程序代码的执行顺序一致。而volatile修饰的变量不会被指令重新排序优化,保证代码的执行顺序与程序的顺序相同。在某些需要确保顺序执行的情况下,volatile关键字就很有用。例如:
//此变量必须定义为volatile
volatile boolean initialied = false;

//以下代码为线程A执行
do_something();//线程A执行初始化相关的操作
....
initialied = true;//执行完初始化


//以下代码为线程B执行
//等待initialied为true,代表线程A初始化完了
while(!initialied){
sleep();
}
do_someting();

在上述代码中,volatile关键字确保了initialized变量不会被编译器优化,这样线程A初始化完之后才会触发线程B执行,而不会出现线程A没有初始化完线程B就开始执行的情况

总之,volatile变量在多线程之间具有可见性,同时volatile变量不会被编译器优化;在某些情况下,volatile变量是线程之间同步的很好选择。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值