【Java 并发】三大特性

news2024/11/24 2:46:33

在 Java 的高并发中,对于线程并发问题的分析通常可以通过 2 个主核心进行分析

  1. JMM 抽象内存模型和 Happens-Before 规则
  2. 三大特性: 原子性, 有序性和可见性

JMM 抽象内存模型和 Happens-Before 规则, 前面我们讨论过了。这里讨论一下三大特性。

1 原子性

定义: 一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断要么就都不执行

简单地说就是一个操作被定义为原子性了, 那么不管这个操作里面包含多少个步骤, 他们都是一个整体的, 这个整体只有执行成功, 或不执行, 不存在任何的中间态, 比如只执行一半, 或者一半成功, 一半失败。

而回到 Java 中, 很多操作看起来就一个操作, 好像是具备原子性, 但是实际中却不具备原子性。

猜猜下面的操作哪些是原子操作?

// 1
int a = 1;

// 2
a++;

// 3
int b = a + 1;

// 4
a = a + 1;

答案是: 只有第一个。

a++ 操作可以拆分为下面 3 步:

  1. 读取 a 的值
  2. a 的值加 1
  3. 将计算后的值重新赋值给 a

其他 2 个的分析类似。

1.1 原子操作

在 Java 内存模型中定义了 8 种原子操作

操作作用对象说明
lock (锁定)主内存中的变量把一个变量标识为一个线程独占的状态
unlock (解锁)主内存中的变量把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
read (读取)主内存的变量把一个变量的值从主内存传输到线程的工作内存中,以便后面的 load 动作使用
load (载入)工作内存中的变量把 read 操作从主内存中得到的变量值放入工作内存中的变量副本
use (使用)工作内存中的变量把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
assign (赋值)工作内存中的变量把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
store (存储)工作内存的变量把工作内存中一个变量的值传送给主内存中以便随后的write操作使用
write (操作)主内存的变量把 store 操作从工作内存中得到的变量的值放入主内存的变量中

上面的这些指令操作是相当底层的,可以作为扩展知识面掌握下。

需要注意的一点就是: 指令与指令之间组合起来达到某个效果。
但是 Java 内存模型只要求这些组合指令之间是顺序执行的,不强制他们一定是连续执行的。

比如把一个变量从主内存中复制到工作内存中就需要执行 read, load 操作,将工作内存同步到主内存中就需要执行 store, write 操作。
也就是说 read 和 load 之间可以插入其他指令,store 和 writer 可以插入其他指令。
比如对主内存中的 a, b 进行访问就可以出现这样的操作顺序: read a, read b, load b, load a

支持变量操作的原子操作的有 read, load, use, assign, store, write。 基础数据类型比较简单, 基本只有使用到其中的一条, 所以可以看为基本数据类型的访问读写具备原子性 (long 和 double 的操作不具备操作性), 如上面的 int a = 1;

1.2 synchronized 和 volatile 对原子性的支持

synchronized

上面一共有八条原子操作,其中六条可以满足基本数据类型的访问读写具备原子性,还剩下 lock 和 unlock 两条原子操作。这 2 个可以用于支持更大范围的原子性操作。
尽管 JVM 没有把 lock 和 unlock 开放给我们使用,但 JVM 以更高层次的指令 monitorenter 和 monitorexit 指令开放给我们使用,
映射到 Java 代码中就是— synchronized 关键字, 也就是说 synchronized 满足原子性。

public void test() {
    int b = 0;
    synchronized(Test.class) {
        int a = 1;
        b = a;
    }
}

volatile

而说到关键字, Java 中另一个和并发相关的高频关键字 volatile, 是否可以保证原子性了。
先举一个例子。

public class VolatileExample {

    private static volatile int counter = 0;
 
    public static void main(String[] args) {

        // 启动 10 个线程
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++)
                        counter++;
                }
            });
            thread.start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(counter);
    }
}

如上: counter 是 volatile 修饰的。
开启 10 个线程,每个线程都自加 10000 次,如果不出现线程安全的问题最终的结果应该就是:10 * 10000 = 100000。
但是运行多次都是小于 100000 的结果, 也就是说明: volatile 不能保证原子性

从上面的说明可以知道 counter++ 不是原子性操作。如果线程 A 读取 counter 到工作内存后,其他线程对这个值已经做了自增操作后,那么线程 A 的这个值自然而然就是一个过期的值,因此,总结果必然会是小于 100000 的。

如果让 volatile 保证原子性,必须符合以下两条规则:

  1. 运算结果并不依赖于变量的当前值,或者能够确保只有一个线程修改变量的值
  2. 变量不需要与其他的状态变量共同参与不变约束 (volatile 变量的变化不会与其它变量的变化有任何联系)

2 有序性

定义: 程序执行的顺序按照代码的先后顺序执行。

例如:

int i = 0;              
boolean flag = false;
i = 1;                // 语句1  
flag = true;          // 语句2

先给变量 i 赋值,然后给 flag 赋值,语句 1 在 语句 2 的前面。
但是在编译器和处理器可能会为了性能对其进行重排序 (指令重排序), 因为语句 1 和 语句 2 之间没有依赖关系,所以语句 2 可能被重排序到语句 1 的前面。

2.1 synchronized 和 volatile 对有序性的支持

synchronized

synchronized 语义表示锁在同一时刻只能由一个线程进行获取,当锁被占用后,其他线程只能等待。
因此, synchronized 语义就要求线程在访问读写共享变量时只能 “串行” 执行, 因此 synchronized 具有有序性。

但是在 synchronized 内部的代码块的逻辑, JVM 没有禁止重排序, 也就是支持处理器为了性能, 在不影响结果的情况下, 调整执行顺序。

例子:

public void test() {
    int b = 0;
    synchronized(Test.class) {
        int a = 1;
        // 1
        b = a;
        // 2
        int c = a;
    }
}

上面 1,2 2 个操作没有存在结果的依赖, 如果为了性能, 处理器仍然可以对他们进行重排序, 变为 2, 1 的执行顺序。

volatile

在 Java 内存模型中说过,为了性能优化,编译器和处理器会进行指令重排序。
也就是说 Java 程序天然的有序性可以总结为:如果在本线程内观察,所有的操作都是有序的, 如果在一个线程观察另一个线程,所有的操作都是无序的。那么 volatile 具备有序性吗?

先看一个例子:volatile 和 双重检验锁定的方式(Double-checked Locking)的关系:

public class Singleton {

    private volatile static Singleton instance;

    private Singleton() { }

    public Singleton getInstance(){
        if(instance == null){
            synchronized (Singleton.class){

                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这里的 instance 为什么要加 volatile 修饰?
创建一个对象,实际是经过 3 步实现的

  1. 分配对象内存空间
  2. 初始化对象
  3. 将对象指向我们刚刚分配的内存

但是不加 volatile 在重排序的作用下, 可能会出现下面的执行顺序:

Alt 'ObjectInitWithoutVolatile'

如果 2 和 3 进行了重排序的话,线程 B 进行判断 if( instance == null) 时就会为 true,而实际上这个 instance 并没有初始化成功,显而易见对线程 B 来说之后的操作就会是错的。
而用 volatile 修饰的话,可以禁止 2 和 3 操作重排序,从而避免这种情况。volatile 包含禁止指令重排序的语义, 其具有有序性。

3 可见性

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

// 线程 1 执行的代码
int i = 0;
i = 10;
 
// 线程 2 执行的代码
j = i;

假若执行线程 1 的是 CPU1,执行线程 2 的是 CPU2。由上面的分析可知:
当线程 1 执行 i = 10 这句时,会先把 i 的初始值加载到 CPU1 的本地缓存,然后赋值为 10,那么在 CPU1 的本地缓存中把 i 的值变为 10,却没有立即写入到主存当中。

此时线程 2 执行 j = i,它会先去主存读取 i 的值并加载到 CPU2 的本地缓存当中,此时内存当中 i 的值还是 0,那么就会使得 j 的值为 0,而不是 10。
这就是可见性问题,线程 1 对变量 i 修改了之后,线程 2 没有立即看到线程 1 修改的值。

3.1 synchronized 和 volatile 对有序性的支持

synchronized
通过对 synchronized 的内存语义进行了分析,当线程获取锁时会从主内存中获取共享变量的最新值,释放锁的时候会将共享变量同步到主内存中。所以 synchronized 具有可见性 (Happens-Before 的 Monitor 规则保证)。

volatile

volatile 修饰的变量在写的时候,底层会在后面在添加 lock 指令,确保将修改的值刷新到主内存,所以 volatile 具备可见性。

4 总结

synchronized 具有: 原子性, 有序性, 可见性。

volatile 具有:有序性,可见性。

synchronized 是否保证有序性呢? 从上面的双重检测看起来, synchronized 貌似不保证有序性, 但是 synchronized 还是保证有序性的, 只是和 volatile 的有序性不一样。

volatile 关键字禁止 JVM 编译器和处理器对其进行重排序, 而 synchronized 保证的有序性是只有单线程可以获取锁, 串行地执行同步代码的结果, 但是同步代码里的语句是会发生指令重排序。
进入 synchronized 代码块前, 底层先添加一个 acquire barrier, 在最后添加一个 release barrier, 保证同步代码块中的代码不能和同步代码块外面的代码进行指令重排, 在其内部还是会发生指令重排但基本不会影响结果。


public void testSynchronized() {

    // 1
    int a = 1;

    // 2
    synchronized(TestDemo.class) {
        // 2.1
        a = 2;
        // 2.2
        int b = 3;
        // 2.3
        int c = 4;
    }

    // 3
    a = 3;
}

将 synchronized 内的代码块看着整个整体, 在 synchronized 的作用下, 1, 2, 3 是有序的,
但是 synchronized 不保证代码块内的代码是有序的, 在没有数据依赖的条件下, 运行指令重排序, 也就是可能存在 2.1 - 2.3 - 2.2 等情况。

5 参考

三大性质总结:原子性、可见性以及有序性

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

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

相关文章

HarmonyOS4.0从零开始的开发教程17给您的应用添加通知

HarmonyOS&#xff08;十五&#xff09;给您的应用添加通知 通知介绍 通知旨在让用户以合适的方式及时获得有用的新消息&#xff0c;帮助用户高效地处理任务。应用可以通过通知接口发送通知消息&#xff0c;用户可以通过通知栏查看通知内容&#xff0c;也可以点击通知来打开应…

基于CNN+数据增强+残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)+数据集+模型(一)

系列文章目录 基于CNN数据增强残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)数据集模型&#xff08;一&#xff09; 基于CNN数据增强残差网络Resnet50的少样本高准确度猫咪种类识别—深度学习算法应用(含全部工程源码)数据集模型&#xf…

Win11极速安装Tensorflow-gpu+CUDA+cudnn

文章目录 0.pip/conda换默认源1.Anacondapython虚拟环境2.安装CUDA以及cudnn测试tensorflow的GPU版本安装成功的办法 0.pip/conda换默认源 为了高效下载&#xff0c;建议先把默认源换了&#xff0c;很简单这里不再赘述。&#xff08;我用梯子&#xff0c;所以没换源&#x1f6…

数据分析(一)(附带实例和源码)

一、主要目的&#xff1a; 主要利用Python包&#xff0c;如Numpy、Pandas和Scipy等常用分析工具并结合常用的统计量来进行数据的描述&#xff0c;把数据的特征和内在结构展现出来。熟悉在Python开发环境中支持数据分析的可用模块以及其中的方法&#xff0c;基于一定的样例数据…

【Java】使用递归的方法获取层级关系数据demo

使用递归来完善各种业务数据的层级关系的获取 引言&#xff1a;在Java开发中&#xff0c;我们通常会遇到层层递进的关系型数据的获取问题&#xff0c;有时是树状解构&#xff0c;或金字塔结构&#xff0c;怎么描述都行&#xff0c;错综复杂的关系在程序中还是可以理清的。 这…

服务器RAID配置及功能介绍

服务器RAID配置及功能介绍 一、RAID磁盘阵列详解1.RAID磁盘阵列介绍2.RAID 03.RAID14.RAID35.RAID56.RAID67.RAID 10总结阵列卡介绍 一、RAID磁盘阵列详解 1.RAID磁盘阵列介绍 ①是Redundant Array of lndependent Disks的缩写中文简称为独立冗余磁盘阵列。 ②把多块独立的物…

nginx_rtmp_module 之 ngx_rtmp_mp4_module 的mp4源码分析

一&#xff1a;整体代码函数预览 static ngx_int_t ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf) {ngx_rtmp_play_main_conf_t *pmcf;ngx_rtmp_play_fmt_t **pfmt, *fmt;pmcf ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);pfmt ngx_ar…

Prometheus 监控笔记(1):你真的会玩监控吗?

认识Prometheus Prometheus 是一种开源的系统和服务监控工具&#xff0c;最初由 SoundCloud 开发&#xff0c;后来成为继 Kubernetes 之后云原生生态系统中的一部分。在 Kubernetes 容器管理系统中&#xff0c;通常会搭配 Prometheus 进行监控&#xff0c;同时也支持多种 Expo…

Node.js安装教程

虽然网上Node.js的安装教程有很多&#xff0c;但是基本上都是千篇一律。虽然跟着网上内容安装&#xff0c;却总会遇到乱七八糟的问题。为此&#xff0c;我写下这篇文章&#xff0c;除了描述node的安装教程&#xff0c;还会解释这样安装的过程起到一个什么作用。 文章大致上分为…

visual studio 2019 移除/卸载项目已经如何再加载项目

文章目录 移除解决方案下的某个项目添加已移除的项目移除项目加载已卸载的项目注意事项 移除解决方案下的某个项目 在项目名称上&#xff0c;点击鼠标右键&#xff0c;弹出右键工具栏&#xff0c;找到 移除 功能。 然后鼠标左键点击 移除。 弹出的模态框&#xff0c;选择确定…

《点云处理》平面拟合

前言 在众多点云处理算法中&#xff0c;其中关于平面拟合的算法十分广泛。本篇内容主要是希望总结归纳各类点云平面拟合算法&#xff0c;并且将代码进行梳理保存。 环境&#xff1a; VS2019 PCL1.11.1 1.RANSAC 使用ransac对平面进行拟合是非常常见的用法&#xff0c;PCL…

josef约瑟 时间继电器 DS-23/C AC220V 10S柜内板前接线

系列型号&#xff1a; DS-21时间继电器 &#xff1b;DS-22时间继电器&#xff1b; DS-23时间继电器&#xff1b;DS-24时间继电器&#xff1b; DS-21C时间继电器&#xff1b;DS-22C时间继电器&#xff1b; DS-23C时间继电器&#xff1b; DS-25时间继电器&#xff1b;DS-26…

Delphi 编译关闭时 Stack overflow 错误

本人工程文件&#xff0c;编译EXE文件&#xff0c;程序关闭时出现 Stack overflow 错误。网搜索一些解决办法&#xff1a;比如&#xff0c;加大堆栈...&#xff0c;均不能问题。虽然&#xff0c;生成的EXE文件&#xff0c;执行时&#xff0c;无任何问题。 Stack overflow 错误&…

【面试】测试/测开(NIG2)

145. linux打印前row行日志 参考&#xff1a;linux日志打印 前10行日志 head -n 10 xx.log后10行日志 tail -n 10 xx.log tail -10f xx.log使用sed命令 sed -n 9,10p xx.log #打印第9、10行使用awk命令 awk NR10 xx.log #打印第10行 awk NR>7 && NR<10 xx.log …

基于JSP+Servlet+Mysql的建设工程监管信息

基于JSPServletMysql的建设工程监管信息 一、系统介绍二、功能展示1.企业信息列表2.录入项目信息3.项目信息列表 四、其它1.其他系统实现五.获取源码 一、系统介绍 项目名称&#xff1a;基于JSPServlet的建设工程监管信息 项目架构&#xff1a;B/S架构 开发语言&#xff1a;…

IEEE、Sci-Hub

最近要写毕业论文&#xff0c;记录一下查询资料的网站。 IEEE&#xff08;Institute of Electrical and Electronics Engineers&#xff09;是世界上最大的专业技术协会之一&#xff0c;致力于推动电气和电子工程领域的创新和发展。IEEE成立于1884年&#xff0c;总部位于美国纽…

【公务员】资料分析——做题技巧

小分互换 1 2 50 % 1 3 33.3 % 1 4 25 % 1 5 20 % 1 6 16.7 % 1 7 14.3 % 1 8 12.5 % 1 9 11.1 % 1 10 10 % 1 11 9.1 % 1 12 8.3 % 1 13 7.7 % 1 14 7.1 % 1 15 6.7 % \frac 1250\% \quad \frac 1333.3\% \quad \frac 1425\% \quad \frac 1520\% \quad \frac 16…

基于CentOS7_安装Docker

基于CentOS7_安装Docker 配置网络&#xff0c;使其能ping通外网 安装依赖包 yum install -y yum-utils device-mapper-persistent-data lvm2下载repo文件 wget -O /etc/yum.repos.d/docker-ce.repo https://repo.huaweicloud.com/docker-ce/linux/centos/docker-ce.repo更换…

基于Springboot的体育馆管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的体育馆管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&a…

【Spring】Spring中的事务

文章目录 1. Spring事务简介2. Spring事务的案例案例代码代码目录结构数据库pom.xmlResource/jdbc.propertiesconfig/SpringConfig.javaconfig/JdbcConfig.javaconfig/MyBatisConfig.javadao/AccountDao.javaservice/AccountService.javaservice/impl/AccountServiceImpl.java测…