Volatile关键字总结

一、Volatile关键字的知识点

  1. volatile能保证线程的可见性,但不能保证线程的原子性
  2. 通过volatile关键字,保证线程每次读取变量是从主存中读取,不是通过L3或者L4缓存中读取
  3. 但问题在于volatile关键字只能保证每次缓存失效,不能保证操作是原子性的,我们知道i++这样的操作是分为3个步骤
    1. 从内存中读取变量
    2. 在线程的栈桢中操作加1
    3. 讲结果写入内存中
  4. 这样在多个线程中存在这样的现象,i=10,线程1读取了i的最新变量值,但是此时线程1遭遇阻塞或者挂起,线程2此时区读取i的最新变量值,此时仍为10,那么线程1和线程2都进行1++操作,那么操作了2次i++,结果为11

以下为代码实例

package com.huyi.thread;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: huyi
 * @Date: 2017/11/28 19:48
 */
public class Test {
    public volatile int inc = 0;

    Lock lock = new ReentrantLock();

    public void increase() {
        lock.lock();
        try {
            inc++;
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final Test test = new Test();
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }

        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();
        System.out.println(test.inc);
    }
}

可以发现结果(请断点执行)不为10000

二、volatile关键字的使用场景

  下面这段话摘自《深入理解Java虚拟机》:

  “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

  lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

  1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

  2)它会强制将对缓存的修改操作立即写入主存;

  3)如果是写操作,它会导致其他CPU中对应的缓存行无效。

  1. volatile能保证可见性以及有序性,通过这些特性我们可以使用:
context = new Context(); //语句1
boolean volatile flag = true; //语句2

while(!flag){ //语句3
    sleep();
}

doSomeThing(context); //语句4

假设线程1执行到语句2,线程2执行到语句3,由于flag使用了volatile关键字,那么能够保证语句1在语句2前执行,那么线程2执行到语句4的时候context是已经实例化,不会抛出NullPointException

  1. 使用双重检查 double-check
public class Singleton {
    private volatile Singleton singleton;

    public static Singleton getSingleton(){
        if(singleton == null){
            synchronized(this){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

可能觉得这段代码很奇怪,为什么要检查2次singleton是否为空,实际上如果我们要写单例模式的话,应该会这样

public class Singleton {
    private volatile Singleton singleton;

    public static synchronized Singleton getSingleton(){
        if(singleton == null){
             singleton = new Singleton();
        }
        return singleton;
    }
}

这样会简单很多,但反过来想想,第一段代码检查2次不是会更好减少线程锁(monitor)的开销吗,如果存在大量的线程操作,如何减少线程开销也是要考虑的问题

参考资料:
1. https://www.cnblogs.com/dolphin0520/p/3920373.html
2. http://blog.csdn.net/dl88250/article/details/5439024

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值