volatile 关键字

volatile

JMM:

内存模型,不是内存布局。每个java线程都有自己的工作内存,操作数据的时候都是从主内存读取,拷贝到自己的工作内存,操作完成写回到主内存。

可能带来可见性、原子性、有序性的问题。

volatile是一种轻量级的同步机制。保证了可见性,有序性,但不能保证原子性。

1. volatile 的可见性测试

class MyData{
    volatile int number=0;
    public void addTO60(){
        this.number=60;
    }
}
/**
 * 1.验证volatile的可见性
 * 1.1加入int number = 0;
 */
public class VolatileDemo{
    public static void main(String[] args) {
        MyData myData = new MyData();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t come in");
            //暂停一会线程
            try {
                Thread.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addTO60();
            System.out.println(Thread.currentThread().getName()+"\t update number valuer"+ myData.number);
        },"AAA").start();

        //第二个线程就是我们的main线程
        while (myData.number==0){

        }
        System.out.println(Thread.currentThread().getName()+"\t mission is over"+ myData.number );
    }
}

mydate资源类,在number没有用volatile修饰是,程序的运行结果是

AAA	 come in
AAA	 update number valuer60

用volatile修饰后

AAA	 come in
AAA	 update number valuer60
main	 mission is over60

2. volatile 没有原子性及解决

全部代码:

class MyData2{
    volatile int number=0;
    public void addTO60(){
        this.number=60;
    }
    public  void addPlusPlus(){
        number++;
    }
    AtomicInteger atomicInteger=new AtomicInteger();
    public  void addMyAtomic(){
        atomicInteger.getAndIncrement();
    }
}
/**
 * 1.验证volatile的可见性
 * 1.1加入int number = 0;number变量之前根本没有添加volatile关键字修饰,没有可见性
 * 1.2 添加了volatile,可以解决可见性问题
 *
 * 2.验证volatile不保证原子性
 * 2.1 原子性是什么?
 *     不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以被分割,同时成功,同时失败
 * 2.2 volatile不保证原子性
 *      为什么? ++操作本身在字节码层面分成了三个步骤,加入三个线程分别执行++ 操作,都先从主内存中拿到最开始的0,
 *      线程1进行++操作后,通知线程2,线程2将自己拿到的数据修改为1,但是线程3执行的也非常快,打断了线程1的通知,造成了写覆盖。
 *      所以最后的结果本来应该是3,但是其实是2或者1.
 * 2.3 怎么解决?
 *      使用AtomicInteger
 */
public class VolatileDemo2{
    public static void main(String[] args) {
        MyData2 myData2 = new MyData2();
        for(int i=1;i<=20;i++){
            new Thread(()->{
                for(int j=1;j<=1000;j++){
                    myData2.addPlusPlus();
                    //开始加入atomicInteger
                    myData2.addMyAtomic();
                }
            },String.valueOf(i)).start();
        }
        //需要等待上面20个线程都计算完成后,再用main线程去的最终的结果看是多少?
       while(Thread.activeCount()>2){//默认后台有两个线程,一个main,一个gc
           Thread.yield();
       }
        System.out.println(Thread.currentThread().getName()+"\t finally number value:"+myData2.number);
        System.out.println(Thread.currentThread().getName()+"\t AtomicInteger type finally number value:"+myData2.atomicInteger);
    }

}

在只有volatile修饰的时候,执行结果有可能是20000,但是大部分时候是不到20000,没有原子性。

解决方法: 1. 加synchronized锁—重量级;
2. 用AtomicInteger类,底层使用的是unsafe的getAndAddInt方法。

​ 源码讲解https://baijiahao.baidu.com/s?id=1647621616629561468&wfr=spider&for=pc

main	 finally number value:19979
main	 AtomicInteger type finally number value:20000

3. volatile有序性

int n = 1;//1
int m = 5;//2
n = n+6;//3
m = n*n;//4

在多线程的情况下,以上的例子在执行的时候可能出现的顺序有1234、2134、1342,但是结果不变。不会出现4首先执行,应为没有先加载n;

这时不需要指令重排。

volatile底层使用cpu的内存屏障 指令,有两个租用,一个是保证特定操作的顺序,二是保证可见性。

4. 那些地方用过volatile

单例模式:

public class SingletomDemo {
    private  static SingletomDemo instance=null;
    private SingletomDemo() {
        System.out.println(Thread.currentThread().getName()+"\t 我是构造方法");
    }
    //DCL模式 双端检索机制
    public static  SingletomDemo getInstance(){
        if(instance == null){
            synchronized (SingletomDemo.class){
                if(instance==null){
                    instance= new SingletomDemo();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        for(int i = 0;i <=10;i++){
            new Thread(()->{
                SingletomDemo.getInstance();
            },String.valueOf(i+1)).start();
        }
    }
}

常见的加锁的单例模式在多线程下还是有可能有线程安全问题;

memory = allocate();//1.分配内存
instance(memory);//2.初始化对象
instance=memory;//3.设置引用地址

2、3 没有数据依赖关系,可能发生指令重排。如果发生,此时内存已经分配,那么instance=memory不是null。如果此时线程挂起,instance(memory)没有执行,对象没有初始化。犹豫instance!=null ,所以两次判断都跳过,最后返回的instance没有任何内容。

解决方法就是在singletondemo的对象上加上volatile。禁止指令重排。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值