volatile关键字三重功效

  1. 64位写入原子性

举一个简单的例子,对于一个long型变量的赋值和取值操作而言,在多线程场景下,线程A调用set(100),线程B调用get(),在某些场景下,返回值可能不是100。

package com.lc.test02;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author liuchao
 * @date 2020/7/3
 */
public class Test01 {

    private long a = 0;

    public void set(long a) {//线程1设置值
        this.a = a;
    }

    public long get() {//线程2获取值,返回值可能不是100
        return a;
    }

    public static void main(String[] args) {
        Test01 test = new Test01();
        ExecutorService executor = Executors.newFixedThreadPool(2);
        CountDownLatch latch = new CountDownLatch(1);
        executor.execute(() -> {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long i = 100L;
            test.set(i);
            System.out.println("-----设置:" + i);
        });
        executor.execute(() -> {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("----获取:" + test.get());
        });
        latch.countDown();
        executor.shutdown();
    }

}

 为什么获取到的值有可能不是100呢,是因为JVM规范并未要求64位的long和double写入是原子性的,64位的数据有可能被拆分成两个32位的数据写入,这样另一个线程拿到的数据有可能是32位的不完整的数据,如果在long属性前面加上volatile关键字就可以解决此问题。

  1. 内存可见性

JVM将内存组织为主内存和工作内存两个部分。

针对主内存中的变量,线程A操作后线程B看到要经过的流程

线程A操作后数据存储在线程A工作内存=》save到主内存=》线程B从主内存load到线程B工作内存

那在整个操作过程中是非原子性操作的,有可能线程A修改后是10,但是线程B读取到的数据是非10,因为线程A修改后的数据还未save到主内存,那要解决这个问题也比较简单就是直接在属性前面加上volatile关键字,也就是解决了内存可见性问题 

  1. 禁止重排序

在经典的单线程安全的写法上DCL

//注意,此代码有安全问题

package com.lc.test02;

/**
 * @author liuchao
 * @date 2020/7/3
 */
public class Test01 {

    private static Test01 instance;

    public static Test01 getInstance() {
        if (null == instance) {
            synchronized (instance) {//为了性能验证 使用lock
                if (null == instance) {
                    instance = new Test01();//有问题的代码
                }
            }
        }
        return instance;
    }

}

上述的instance = new Test01();代码有问题:其底层会分为三个操作:

(1)分配一块内存。

(2)在内存上初始化成员变量。

(3)把instance引用指向内存。 

在这三个操作中,操作(2)和操作(3)可能重排序,即先把instance指向内存,再初始化成员变量,因为二者并没有先后的依赖关系。此时,另外一个线程可能拿到一个未完全初始化的对象。这时,直接访问里面的成员变量,就可能出错。这就是典型的“构造函数溢出”问题。解决办法也很简单,就是为instance变量加上volatile修饰

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈行动派

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值