java volatile 关键字详解「建议收藏」

java volatile 关键字详解「建议收藏」javavolatile关键字详解一,什么是volatile关键字,作用是什么​volatile是java虚拟机提供的轻量级同步机制​作用是:1.保证可见性2.禁止指令重排3.不保证原子性本篇具体就讲解什么叫保证了可见性,什么叫禁止指令重排,什么是原子性而在这之前需要对JMM有所了解二,什么是JMM​JMM(java内存模型JavaMemoryModel简称JMM)本身是一个抽象的概念,并不在内存中真实存在的,它描述的

大家好,又见面了,我是你们的朋友全栈君。

java volatile 关键字详解

一,什么是volatile关键字,作用是什么

​ volatile是java虚拟机提供的轻量级同步机制

​ 作用是: 1.保证可见性 2.禁止指令重排 3.不保证原子性

本篇具体就讲解 什么叫保证了可见性, 什么叫禁止指令重排,什么是原子性

而在这之前需要对JMM 有所了解

二,什么是JMM

​ JMM(java 内存模型 Java Memory Model 简称JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量(实例字段,静态字段和构成数组对象的元素)的访问方式.

JMM的同步规定:

​ 1.线程解锁之前,必须把共享变量刷新回主存

​ 2.线程加锁锁之前,必须读取主存的最新值到自己的工作空间

​ 3.加锁解锁必须是 同一把锁

​ 由于 JMM运行程序的实体是线程.而每个线程创建时JMM都会为其创建一个自己的工作内存(栈空间),工作内存是每个线程的私有 数据区域.而java内存模型中规定所有的变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问,但线程的变量的操作(读取赋值等)必须在自己的工作内存中去进行,首先要 将变量从主存拷贝到自己的工作内存中,然后对变量进行操作,操作完成后再将变量操作完后的新值写回主内存,不能直接操作主内存的变量,各个线程的工作内存中存储着主内存的变量拷贝的副本,因IC不同的线程间无法访问对方的工作内存,线程间的通信必须在主内存来完成, 其简要访问过程如下图:

在这里插入图片描述

三,可见性

​ 可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

​ 通过前面的 JMM介绍,我们知道各个线程对主内存的变量的操作都是各个线程各自拷贝到自己的工作内存中进行操作,然后在写回主内存中

​ 这就可能存在一个线程a修改了共享变量X的值但还未写回主内存,又有一个线程b对共享变量X进行操作,但 此时线程a的工作内存的共享变量X对线程吧来说是不可见的,这种工作内存与主内存同步延迟的问题就造成了可见性问题

四,不保证原子性

​ 原子性:某个线程在执行某项业务时,中间不可被加塞或分割,需要整体完整。要么同时成功,要么同时失败

    class MyData{ 
   volatile int number = 0;
​    Object object = new Object();

    public void addTo60(){ 
   
        this.number = 60;
    }
    
    public void addPlusPlus(){ 
   
        this.number++;
    }
    
    AtomicInteger atomicInteger = new AtomicInteger();
    
    public void addAtomic(){ 
   
        atomicInteger.getAndIncrement();
    }
}

/** * 验证volatile的可见性 * 1.当number未被volatile修饰时,new Thread将number值改为60,但main线程并不知道,会一直在循环中出不来 * 2.当number使用volatile修饰,new Thread改变number值后,会通知main线程主内存的值已被修改,结束任务。体现了可见性 * * 验证volatile不保证原子性 * 1.原子性是指,某个线程在执行某项业务时,中间不可被加塞或分割,需要整体完整。要么同时成功,要么同时失败 * * 如何解决呢? * 1.使用synchronize * 2.使用AtomicInteger * */
public class VolatileDemo { 
   
    public static void main(String[] args) { 
   
        //seeByVolatile();
        atomic();
    }
    //验证原子性
    public static void atomic() { 
   
        MyData myData = new MyData();
        for (int i = 1; i <= 20; i++) { 
   
            new Thread(new Runnable() { 
   
                @Override
                public void run() { 
   
                    for (int j = 1; j <= 1000; j++) { 
   
                        /*synchronized (myData.object){ myData.addPlusPlus(); }*/
                        myData.addPlusPlus();
                        myData.addAtomic();
                    }
                }
            }).start();
        }

        //等待上面20个线程全部计算结束
        while (Thread.activeCount() > 2){ 
   
            Thread.yield();
        }
        
        System.out.println(Thread.currentThread().getName() + "int finally number is " + myData.number);
        System.out.println(Thread.currentThread().getName() + "AtomicInteger finally number is " + myData.atomicInteger);
    }

    //验证可见性的方法
    public static void seeByVolatile() { 
   
        MyData myData = new MyData();
        //第一个线程
        new Thread(){ 
   
            public void run(){ 
   
                System.out.println(Thread.currentThread().getName() + " come in");
                try { 
   
                    sleep(3000);
                } catch (InterruptedException e) { 
   
                    e.printStackTrace();
                }
                myData.addTo60();
                System.out.println(Thread.currentThread().getName() + " update number to " + myData.number);
            }
        }.start();

        //第二个线程 main
        while (myData.number == 0){ 
   
        
        }
        System.out.println(Thread.currentThread().getName() + "mission is over");
    }
}

number++在多线程下是非线程安全,不是原子性操作?

在这里插入图片描述

五,禁止指令重排

​ 计算机在执行程序时,为了提高性能,编译器和处理 器常常会对指令做重排,一般分为一下三种:

在这里插入图片描述

单线程的环境里指令重排确保最终执行的结果和代码顺序执行的结果一致

处理器在进行指令重排是必须 要考虑指令之间的数据依赖性

多线程的环境交替执行,由于编译器优化重排的存在,俩个线程使用变量能否保证一致性是无法确定的,无法预料的

实例一:

在这里插入图片描述

实例二:

在这里插入图片描述

线程操作资源类,线程1访问method1,线程2访问method2,正常情况顺序执行,a=6
多线程下假设出现了指令重排,语句2在语句1之前,当执行完flag=true后,另一个线程马上执行method2,a=5

所以volatile 禁止指令重排,从而避免多线程的 环境下出现执行乱序 的情况

六:使用volatile 的经典案例

 单例DCL的代码

public class SingletonDemo { 
   
    private static SingletonDemo instance = null;

    private SingletonDemo(){ 
   
        System.out.println(Thread.currentThread().getName() + "构造方法");
    }
    
    //DCL双端加锁机制
    public static SingletonDemo getInstance(){ 
   
        if (instance == null){ 
   
            synchronized (SingletonDemo.class){ 
   
                if (instance == null){ 
   
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }
}

这种写法在多线程条件下可能正确率为99.999999%,但可能由于指令重排出错

原因在于某一个线程执行到第一次检测,读取到instance不为null,instance引用对象可能还没有完成初始化.

instance = new SingletonDemo();; 分为一下三步

  1. memory = allocate() //分配内存
  2. ctorInstanc(memory) //初始化对象
  3. instance = memory //设置instance指向刚分配的地址

2 ,3 步不存在数据依赖, 可以指令重排的执行顺序为 1 ,3 ,2,设置instance指向刚分配的地址,次数instance还没有初始化完

但此时instance不为null了,若正好此时有一个线程来访问,就出现了线程安全问题

所以需要添加volatile 关键字

public class SingletonDemo { 
   
    private static volatile SingletonDemo instance = null;

    private SingletonDemo(){ 
   
        System.out.println(Thread.currentThread().getName() + "构造方法");
    }
    //DCL双端加锁机制
    public static SingletonDemo getInstance(){ 
   
        if (instance == null){ 
   
            synchronized (SingletonDemo.class){ 
   
                if (instance == null){ 
   
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/161256.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月18日 上午9:36
下一篇 2022年7月18日 上午9:46


相关推荐

  • windows下nginx启动一闪而过(原因以及查看和解决的办法)「建议收藏」

    windows下nginx启动一闪而过(原因以及查看和解决的办法)「建议收藏」解决问题的思路清晰比确切解决的办法更加有效原因:这是80端口被占用的缘故,修改下端口即可。得出此原因的方法:运行“nginx.exe”文件即可,运行后,界面一闪而过。这是查看log日志,就能得到原因2018/08/2321:43:34[emerg]16612#13696:bind()to0.0.0.0:80failed(10013:Anatt…

    2025年8月14日
    3
  • SO库版本号管理

    SO库版本号管理编写SO库文件时可以输入版本号调用SO库文件中特定的函数可以输出版本号

    2022年6月17日
    48
  • Redis事务详解

    Redis事务详解若对事务概念不清楚 请先阅读 彻底理解 MySQL 四种事务隔离级别 这篇文章 链接如下 彻底理解 MySQL 四种事务隔离级别 YaoYong BigData 的博客 CSDN 博客转入正题 结合关系型数据库的事务来看看 Redis 中事务有什么不同 Redis 事务是指将多条命令加入队列 一次批量执行多条命令 每条命令会按顺序执行 事务执行过程中不会受客户端传入的命令请求影响 Redis 事务的相关命令如下 MULTI 标识一个事务的开启 即开启事务 EXEC 执行事务中的所有命令 即提

    2025年10月14日
    5
  • 简单回归模型:普通最小二乘法OLS(一)

    简单回归模型:普通最小二乘法OLS(一)简单回归模型基本概念回归分析 在其他条件不变的情况下 考察一个变量对另一个变量的影响 X 自变量解释变量 Y 因变量被解释变量设变量 u 表示关系式中的干扰项 表示除 X 之外其他影响 Y 的因素 我们用一个简单的方程来表示它们之间的关系 Y 0 1x uY beta 0 beta 1x uY 0 1 x u 当 X 发生变化时 Y 1 X u triangleY beta 1 triangleX triangleu Y 1 X u 如果 u 0 t

    2026年3月26日
    3
  • [导入]sqlserver 判断临时表是否存在语句.

    [导入]sqlserver 判断临时表是否存在语句.

    2021年7月28日
    57
  • python-PyCharm安装使用教程

    python-PyCharm安装使用教程1 下载 Pycharm 在 Pycharm 的官网即可下载 https www jetbrains com pycharm download section windows 下载时有两种版本选择 Professional 专业版 收费 和 Community 社区版 免费 2 开始安装 Pycharm 第一步 直接点击 Next 第二步 如果要修改安装路径 就在这里更改第三步 需要进行一些设置 没有特殊需要按照图中勾选即可 如果有特殊需要 请按如下描述确定是否勾选设置 1 创建快捷方式 根据

    2026年3月27日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号