JMM(Java内存模型)及volatile关键字

news2024/10/2 10:31:43

JMM(Java内存模型 Java Memory Model,简称JMM)

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

JMM关于同步的规定:

1、线程解锁前,必须把共享变量的值刷新回主内存

2、线程加锁前,必须读取主内存的最新值到自己的工作内存

3、加锁解锁是同一把锁

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

例如:将变量age由25修改为37

volatile关键字的作用?

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

主要有三大特性:保证可见性、不保证原子性、禁止指令重排

  • 共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义。

  • 保证不同线程对这个共享变量进行操作时,有可见性,就是其中一个线程对该变量值进行修改,其他线程是马上可见的,volatile关键字会强制将修改的值同步到主内存

  • 禁止指令重排,禁止编译器优化代码顺序,避免在单例Double Check中导致多次初始化,保证有有序性。

  • 一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  • 禁止进行指令重排序

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量, 只有当前线程可以访问该变量,其他线程被阻塞住。

  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。

  • volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性。

  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

(1)可见性代码验证说明

package com.lc.mono.module.camas.utils.common;

import java.util.concurrent.TimeUnit;

class MyData {
    int number = 0;

    public void addT060() {
        this.number = 60;
    }
}

/**
 * 验证volatile的可见性
 * 假如int number =0;number变量之前根本没有添加volatile关键字修饰
 */
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 {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addT060();
            System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.number);
        }, "AAA").start();

        // 第二个线程就是main线程
        while (myData.number == 0) {
            // main线程就一直等待循环,直到number!=0
        }

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

输出信息:

AAA线程已经把number由0修改为60,main线程不可见,因此一直等待。

添加volatile关键字

package com.lc.mono.module.camas.utils.common;

import java.util.concurrent.TimeUnit;

class MyData {
    volatile int number = 0;

    public void addT060() {
        this.number = 60;
    }
}

/**
 * 验证volatile的可见性
 * 假如int number =0;number变量之前根本没有添加volatile关键字修饰
 */
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 {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            myData.addT060();
            System.out.println(Thread.currentThread().getName() + "\t update number value: " + myData.number);
        }, "AAA").start();

        // 第二个线程就是main线程
        while (myData.number == 0) {
            // main线程就一直等待循环,直到number!=0
        }

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

只要有一个修改了主物理内存的值,只要是添加了volatile关键字的变量,其他线程迅速收到最新通知,修改对其他线程可见。

(2)不保证原子性代码验证说明

package com.lc.mono.module.camas.utils.common;

import java.util.concurrent.TimeUnit;

class MyData {
    volatile int number = 0;

    public void addT060() {
        this.number = 60;
    }

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

/**
 * 1 验证volatile的可见性
 * 1.1 假如int number =0;number变量之前根本没有添加volatile关键字修饰
 * 1.2 添加volatile,可以解决可见性问题
 * 2 验证volatile不保证原子性
 * 2.1 原子性指的是什么意思?
 * 不可分割、完整性,也即某个线程正在做某个具体业务的时候,中间不可以被加塞或者被分割。需要整体完整
 * 要么同时成功,要么同时失败。
 * 2.2 是否可以保证原子性?
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();

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

        // 需要等待上面20个线程全部计算完成之后,再用main线程取得最终的结果值看是多少?
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
    }
}

最终的值应该是20000,但是由于不保证原子性导致数据丢失,导致数据小于20000。

volatile不保证原子性问题解决

  • 加synchronized

  • 使用我们JUC下的AtomicInteger (CAS)

package com.lc.mono.module.camas.utils.common;

import java.util.concurrent.atomic.AtomicInteger;

class MyData {
    volatile int number = 0;

    public void addT060() {
        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,可以解决可见性问题
 * <p>
 * 2 验证volatile不保证原子性
 * 2.1 原子性指的是什么意思?
 * 不可分割、完整性,也即某个线程正在做某个具体业务的时候,中间不可以被加塞或者被分割。需要整体完整
 * 要么同时成功,要么同时失败。
 * 2.2 volatile不保证原子性案例演示
 * 2.3 why
 * 2.4 如何解决原子性
 * 加synchronized
 * 使用我们JUC下的AtomicInteger (CAS)
 */
public class VolatileDemo {
    public static void main(String[] args) {
        MyData myData = new MyData();

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

        // main线程需要等待上面20个线程全部计算完成之后,再用main线程取得最终的结果值看是多少?
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + "\t int type, finally number value: " + myData.number);
        System.out.println(Thread.currentThread().getName() + "\t AtomicInteger type, finally number value: " + myData.atomicInteger);
    }
}

(3)volatile禁止指令重排

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

单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。

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

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

案例一:
public void test() {
    int x = 11; //语句1
    int y = 12; //语句2
    x = x + 5; //语句3
    y = x * x; //语句4
}

重排的顺序可能是:语句1234、语句2134、语句1324

语句4不可以重排变成第一个,因为处理器在进行重排序的时候必须要考虑指令之间的数据依赖

案例二:

(1)说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。

(2)指令重排导致结果输出可能是5或者6

a=1; flag=true;

可能重排为 flag=true; a=1; 导致结果出现5。

总结

volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/352566.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

干货分享|数据可视化报表制作技巧

脑中想得再好&#xff0c;也要看最终的效果呈现。但偏偏有些用户分析思维不差&#xff0c;就是数据分析报表的制作拖了后腿&#xff0c;导致始终无法完美呈现数据可视化分析效果。本文将总结奥威BI软件上的常用的数据可视化报表制作技巧&#xff0c;供大家随时查阅。 BI数据可…

搭建Hexo博客-第1章-Git和GitHub以及Coding的简单用法

搭建Hexo博客-第1章-Git和GitHub以及Coding的简单用法 搭建Hexo博客-第1章-Git和GitHub以及Coding的简单用法 Coding GitHub Hexo Markdown 搭建博客 大家好&#xff0c;这是我第一次写博客。使用 GitHub Hexo 创建最基本的博客很容易&#xff0c;网上有很多现成的教程。…

SCI论文写作神器集合 —— 超级实用

特此声明&#xff1a; 本文拷贝多处别人的内容&#xff0c;并给出具体的链接 本文所提到的软件都为博主在文章撰写过程中发掘的比较实用的工具&#xff0c;旨在帮助小伙伴们更快更有效率的完成文章发表&#xff0c;如果其他好用的工具&#xff0c;欢迎各位交流~~ 一、文献搜索神…

XCP实战系列介绍14-基于Vector_Davinci工具的XCP配置介绍(三)

本文框架 1.概述2. 其他模块配置2.1 XCP初始化3. 手工代码部分3.1 周期函数添加3.2 DAQ Event调用3.3 XCP模块本身代码3.4 标定量的添加1.概述 在对XCP的配置部分介绍中我们计划分别对通讯部分配置、XCP模块本身配置及其他相关模块配置三篇进行介绍,在前两篇我们介绍了XCP配置…

SAP PP工单确认完成(CNF)状态取消方法

这SAP PP工单确认完成&#xff08;CNF&#xff09;状态取消方法SAP PP工单确认完成&#xff08;CNF&#xff09;状态取消方法SAP PP工单确认完成&#xff08;CNF&#xff09;状态取消方法 工单完工后取消了其中的一个报工&#xff0c;然后无法再报工 此时再报工&#xff0c;系…

使用 Docker 镜像

author: aming email: jikcheng163.com title: Docker 使用镜像 creation_date: 2023-01-05 22:58 Last modified date: 2023-01-30 23:01 tags: Docker 使用镜像 File Folder with relative path: reading notes/doc/Dokcer 实践 remark: other: 本章背景知识 1、镜像是三大…

Allegro走线时如何自动关闭其它网络飞线显示操作指导

Allegro走线时如何自动关闭其它网络飞线显示操作指导 在做PCB设计的时候,尤其是在评估布线的时候,走某一个网络的时候,希望其它网络的飞线会被自动关闭,方便评估。 Allegro支持这个功能,如下图 走线前 走线后 具体操作如下 点击Route

Spring3事务

简介 数据库事务是数据库管理系统执行过程中的一个逻辑单位&#xff0c;由一个有限的数据库操作序列构成&#xff1b;在企业级开发应用中&#xff0c;事务管理是必不可少的技术&#xff0c;它被用来保证数据的完整性和一致性 事务的四大特性(ACID) 原子性(Atomicity)&#xf…

【黑马SpringCloud(7)】分布式事务

分布式事务事务的ACID原则分布式事务理论基础CAP定理BASE理论Seataseata的部署seata的集成事务模式XA模式Seata的XA模型优缺点实现XA模式AT模式案例&#xff1a;AT模式更新数据脏写问题优缺点实现AT模式TCC模式流程分析Seata的TCC模型事务悬挂和空回滚实现TCC模式优缺点SAGA模式…

MySQL8.x group_by报错的4种解决方法

在我们使用MySQL的时候总是会遇到各种各样的报错&#xff0c;让人头痛不已。其中有一种报错&#xff0c;sql_modeonly_full_group_by&#xff0c;十分常见&#xff0c;每次都是老长的一串出现&#xff0c;然后带走你所有的好心情&#xff0c;如&#xff1a;LIMIT 0, 1000 Error…

《Qt6开发及实例》6-2 Qt6基础图形的绘制

目录 一、绘图框架设计 二、绘图区的实现 2.1 PaintArea类 2.2 PaintArea类讲解 三、主窗口的实现 3.1 MainWidget类 3.2 MainWidget类讲解 3.3 槽函数编写 3.5 其他内容 一、绘图框架设计 界面 两个类 ​ 二、绘图区的实现 2.1 PaintArea类 ​paintarea.h #ifndef…

uniApp消息推送(极光/阿里云)

目录 一、极光推送 1.1、在极光官网创建应用 1.2、插件下载 1.3、代码填充 1.4、发送通知/消息 二、阿里云推送 2.1、在阿里云官网创建应用 2.2、插件下载 2.3、代码填充 2.4、发给后端的值(API类型的通知 一、极光推送 1.1、在极光官网创建应用 参考 极光文档 (ji…

c/c++开发,无可避免的模板编程实践(篇三)

一、模板与多态 多态就是通过单一命名标记关联不同特定行为的能力。在C中&#xff0c;主要是通过继承和虚函数来实现&#xff0c;由于继承和虚函数主要是在运行期进行处理&#xff0c;因此c把这种多态称为“动多态”。而通过函数重载方式也可以单一命名标记关联不同行为&#x…

TrueNas篇-硬盘直通

硬盘直通 在做硬盘直通之前&#xff0c;在trueNas(或者其他虚拟机)内是检测不到安装的硬盘的。 在pve节点查看硬盘信息 打开pve的shell控制台 输入下面的命令查看硬盘信息&#xff1a; ls -l /dev/disk/by-id/该命令会显示出实际所有的硬盘设备信息&#xff0c;其中ata代…

Python 给视频添加背景音乐 | Python工具

目录 前言 环境依赖 代码 总结 前言 本文提供给视频添加背景音乐的python工具&#xff0c;一如既往的实用主义。 环境依赖 ffmpeg环境安装&#xff0c;可以参考我的另一篇文章&#xff1a;windows ffmpeg安装部署_阿良的博客-CSDN博客 本文主要使用到的不是ffmpeg&#x…

绘制正余弦曲线中的sin(x),cos(x)的使用

目录一、 基础知识1.1 头文件1.2 原型1.3 参数1.4 返回值二、使用1. 坐标与弧度的对应关系一、 基础知识 1.1 头文件 #include <math.h> 1.2 原型 double sin(double x) double cos(double x) 1.3 参数 参数是弧度制&#xff08;rad&#xff09; 1.4 返回值 返…

Python 采集 筷 实现视频批量保存

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! 刷到的视频怕它下架&#xff1f;我们来采集保存一下它 知识点: 动态数据抓包 requests发送请求 json数据解析 开发环境: python 3.8 运行代码 pycharm 2022.3 辅助敲代码 requests pip install requests 代码展示 需…

故障案例:MySQL唯一索引有重复值,官方却说This is not a bug

GreatSQL社区原创内容未经授权不得随意使用&#xff0c;转载请联系小编并注明来源。GreatSQL是MySQL的国产分支版本&#xff0c;使用上与MySQL一致。作者&#xff1a;飞鱼过天文章来源&#xff1a;GreatSQL社区原创 问题原因故障解决方案复现步骤参考文献 一、问题&#xff1a;…

图片压缩怎么弄?多种图片格式压缩大小的方法

平时接触的图片格式有许多种&#xff0c;比如jpg、png、gif、tiff、webp等&#xff0c;不同的场景都需要用不同的图片&#xff0c;但是当这些图片大小都不符合我们的使用要求时&#xff0c;该怎么去压缩图片大小呢&#xff1f;小编今天给大家分享一款支持多种图片格式压缩工具&…

openEuler RISC-V 成功适配 VisionFive 2 单板计算机

近日&#xff0c;RISC-V SIG 成功在 VisionFive 2 开发板上适配欧拉操作系统&#xff0c;目前最新版本的 openEuler RISC-V 22.03 V2 镜像已在 VisionFive 2 开发板上可用&#xff0c;这是 openEuler 推动 RISC-V 生态演进的又一新进展。下载链接​​https://mirror.iscas.ac.c…