栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA Volatile关键字 使用以及源码分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA Volatile关键字 使用以及源码分析

Volatile特点

被volatile修改的变量有2大特点


①. 特点:可见性、有序性、不保证原子性

②. volatile的内存语义

1、当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。
2、当读一个volatile变量时,JMM会把该线程对应的本地内存设置为无效,直接从主内存中读取共享变量
3、所以volatile的写内存语义是直接刷新到主内存中,读的内存语义是直接从主内存中读取

volatile凭什么可以保证可见性和有序性

内存屏障
 

有序性

不存在数据依赖关系  可以重排序

      存在数据依赖关系 禁止重排序

什么是内存屏障

①. 内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性

②. 内存屏障之前的所有写操作都要回写到主内存
内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)

③. 一句话:对一个volatile域的写, happens-before于任意后续对这个volatile域的读,也叫写后读

 

内存屏障分类

粗分

细分

源码分析内存屏障指令的由来
(1). IDEA工具里面找Unsafe.class

(2). Unsafe.java
(3). Unsafe.cpp
(4). OrderAccess.hpp
(5). orderAccess_linux_x86.inline.hpp

所以最终细分有4分钟

LoadLoad 

StoreStore 

LoadStore

StoreLoad 

 

深入内存屏障

 

 happens-before之volatile变量规则

内存屏障策略

读屏障

 读插入内存屏障的指令序列示意图

 写屏障

写插入内存屏障的指令序列示意图

volatile特性   volatile特性 - 保证可见性

保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可以看到

代码如下

public class VolatileSeeDemo
{
    static          boolean flag = true;       //不加volatile,没有可见性

   public static void main(String[] args)
    {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"t"+"---come in");
            while(flag)
            {

            }
            System.out.println("t1 over");
        },"t1").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

      flag =false;

        System.out.println(Thread.currentThread().getName()+"t 修改完成 flag: "+ flag);
    }
}

 测试结果

 程序并没有停止

原因:   主线程 将 flag修改为 false 没有将最新的数据刷新到主内存,也没有通知其他线程flag已发生改变,让他重新去主内存拉取最新数据

修改代码

public class VolatileSeeDemo
{
    static    volatile      boolean flag = true;       //不加,没有可见性

   public static void main(String[] args)
    {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"t"+"---come in");
            while(flag)
            {

            }
            System.out.println("t1 over");
        },"t1").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

      flag =false;

        System.out.println(Thread.currentThread().getName()+"t 修改完成 flag: "+ flag);
    }
}

结果

上面问题 原理解释:

volatile变量读写过程

 

 

 volatile特性 - 不保证原子性

测试代码如下

理想 结果 是 10000

class MyNumber
{
     volatile int number = 0;

    public void addPlusPlus()
    {
        number++;
    }
}

public class VolatileNoAtomicDemo
{
    public static void main(String[] args) throws InterruptedException
    {
        MyNumber myNumber = new MyNumber();

        for (int i = 1; i <=10; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    myNumber.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "t" + myNumber.number);
    }
}

测试结果

 每次运行结果都不一样

读取赋值一个变量的情况

不保证原子性的原因

可以在 《深入jvm虚拟机 》第12章3.3可以查看

从底层来看i++ 问题

结论 

 

volatile特性 - 禁止指令重排

指令重排是什么

不存在数据依赖性  可以重排

存在数据依赖性  禁止重排

volatile底层实现

volatile底层是根据内存屏障实现的

volatile有关的禁止指令重排的行为

四大内存屏障插入情况

代码说明

添加 这个 StoreStore  是禁止将 这个flag =true 重新排序  ,如果没有 这个volatile 修饰 可能 会将 flag =true  会优化到第一行 ,结果有多个线程调用这个read 方法 if(flag)为true  但是 i 还没有进行赋值 ,此时 结果与预想结果有误 ,造成语义错误

StoreLoad 指令 就是禁止 flag =true  与下面的 volatile 读或者写重排序

添加 LoadLoad指令 只要有 volatile 写 此时主内存的数据已经是最新的 必须去从主内存中拿最新数据  只有我读完了 确认了在进行后面操作

 
添加 LoadStore 指令  只有volatile读完了 以后再 进行后面操作  从而保证语义正确性和一致性

volatile使用场景

 单一赋值可以,but含复合运算赋值不可以(i++之类)

volatile int a = 10
volatile boolean flag = false

判断业务是否结束

public class UseVolatileDemo{
    private volatile static boolean flag = true;
    public static void main(String[] args){
        new Thread(() -> {
            while(flag) {
                //do something......
            }
        },"t1").start();

        //暂停几秒钟线程
        try { TimeUnit.SECONDS.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            flag = false;
        },"t2").start();
    }
}

开销较低的读,写锁策略

public class UseVolatileDemo{
   
    public class Counter{ 
        private volatile int value;
        public int getValue(){
            return value;   //利用volatile保证读取操作的可见性
         }
        public synchronized int increment(){
            return value++; //利用synchronized保证复合操作的原子性
         }
    }
}
 

单列模式 DCL双端锁的发布

原始代码

在单线程看待这个问题

没加volatile可能出现重排序

在多线程看待这个问题

也就是  第二步和第三步执行顺序 对调了  导致对象还没有初始化完成  就引用指向刚开辟的空间 导致其他现在得到一个null

问题解决

public class SafeDoubleCheckSingleton{
    //通过volatile声明,实现线程安全的延迟初始化。
    private volatile static SafeDoubleCheckSingleton singleton;
    //私有化构造方法
    private SafeDoubleCheckSingleton(){
    }
    //双重锁设计
    public static SafeDoubleCheckSingleton getInstance(){
        if (singleton == null){
            //1.多线程并发创建对象时,会通过加锁保证只有一个线程能创建对象
            synchronized (SafeDoubleCheckSingleton.class){
                if (singleton == null){
                    //隐患:多线程环境下,由于重排序,该对象可能还未完成初始化就被其他线程读取
                                      //原理:利用volatile,禁止 "初始化对象"(2) 和 "设置singleton指向内存空间"(3) 的重排序
                    singleton = new SafeDoubleCheckSingleton();
                }
            }
        }
        //2.对象创建完毕,执行getInstance()将不需要获取锁,直接返回创建对象
        return singleton;
    }
}

有不加volatile的方法解决单例模式的懒汉线程问题 

采用静态内部类的方式实现

public class SingletonDemo {
    private SingletonDemo() { }

    private static class SingletonDemoHandler {
        private static SingletonDemo instance = new SingletonDemo();
    }

    public static SingletonDemo getInstance() {
        return SingletonDemoHandler.instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                SingletonDemo instance = getInstance();
                // 可以知道这里获取到的地址都是同一个
                System.out.println(instance);
            },String.valueOf(i)).start();
        }
    }
}

采用枚举 也可以解决线程安全问题 

public enum Singleton {
    INSTANCE;

    public void sayHello() {
        System.out.println("Hello World");
    }
}

总结

 

volatile 可见性

 

volatile 禁止重排

 

1  写指令

 

2 读指令

 

 

volatile 与内存屏障

 

内存屏障是什么

 

内存屏障能干啥

 

内存屏障四大指令

三句话总结 

 

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1003574.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号