并发编程-线程安全:volatile、Lock、synchronized、CAS、ABA、AQS

news2024/11/24 15:30:53

并发编程-线程安全

  • 1、volatile 关键字
    • 1.1 作用
    • 1.2 底层实现原理
  • 2、synchronized
    • 2.2 synchronized 用法
    • 2.2 synchronized 和 volatile 的区别
  • 3、Lock
    • 3.1 Lock 和 synchronized 的区别
    • 3.2 ReentrantLock 和 synchronized 的区别
  • 4、CAS
    • 4.1 CAS 执行流程
    • 4.2 ABA问题
  • 5、AQS(AbstractQueuedSynchronizer :用于实现各种同步器的抽象类)
    • 5.1 核心思想
    • 5.2 资源共享模式

1、volatile 关键字

1.1 作用

volatile 是一种关键字,用于保证多线程情况下共享变量的可见性。当一个变量被声明为 volatile 时,每个线程在访问该变量时都会立即刷新其本地内存(工作内存)中该变量的值,确保所有线程都能读到最新的值。并且使用 volatile 可以禁止指令重排序,这样就能有效的预防,因为指令优化(重排序)而导致的线程安全问题。也就是说 volatile 有两个主要功能:保证内存可见性禁止指令重排序。下来我们具体来看这两个功能

例如:单例模式的双重校验锁使用volatile保证变量的可见性以及禁止指令重排序。(指的是单例模式的懒汉模式中的私有变量要加 volatile)懒汉模式指的是对象的创建是懒加载的方式,并不是在程序启动时就创建对象,而是第一次被真正使用时才创建对象。

单例模式使用volatile主要是为了 防止指令的重排序。从而避免多线程执行的情况下,因为指令重排序而导致某些线程得到一个未被完全实例化的对象,从而导致程序执行出错的情况

public class Singleton {
    //volatile 防止指令重排 和可见性
    private static volatile Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        //先判断对象是否已经实例化过,没有实例化才进入加锁代码
        if (null == instance) {
            //类对象加锁
            synchronized (Singleton.class) {
                //避免 singleTon== null时,第一个线程实例化后,
                // 进入阻塞状态的线程被唤醒后仍会进行实例化。
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

1.2 底层实现原理

内存可见性volatile 内存可见性主要通过 lock 前缀指令实现的,它会锁定当前内存区域的缓存(缓存行),并且立即将当前缓存行数据写入主内存(耗时非常短),回写主内存的时候会通过 MESI 协议使其他线程缓存了该变量的地址失效,从而导致其他线程需要重新去主内存中重新读取数据到其工作线程中。
指令重排序:指令重排序是通过内存屏障来实现的。

内存屏障(Memory Barrier 或 Memory Fence)是一种硬件级别的同步操作,它强制处理器按照特定顺序执行内存访问操作,确保内存操作的顺序性,阻止编译器和 CPU 对内存操作进行不必要的重排序。内存屏障可以确保跨越屏障的读写操作不会交叉进行,以此维持程序的内存一致性模型。

写内存屏障(Store Barrier / Write Barrier): 当线程写入 volatile 变量时,JMM 会在写操作前插入 StoreStore 屏障,确保在这次写操作之前的所有普通写操作都已完成。接着在写操作后插入 StoreLoad 屏障,强制所有后来的读写操作都在此次写操作完成之后执行,这就确保了其他线程能立即看到 volatile 变量的最新值。

读内存屏障(Load Barrier / Read Barrier): 当线程读取 volatile 变量时,JMM 会在读操作前插入 LoadLoad 屏障,确保在此次读操作之前的所有读操作都已完成。而在读操作后插入 LoadStore 屏障,防止在此次读操作之后的写操作被重排序到读操作之前,这样就确保了对 volatile 变量的读取总是能看到之前对同一变量或其他相关变量的写入结果。

volatile 关键字能保证变量的可见性,但不能保证对变量的操作是原子性的。synchronized可以保证这两个

2、synchronized

2.2 synchronized 用法

synchronized 是 Java 中的一个关键字,翻译成中文是同步的意思,主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行

synchronized 修饰实例方法:(锁当前对象实例)

//给当前对象实例加锁,进入同步代码前要获得当前对象实例的锁 。
synchronized void method() {
    //业务代码
}

** 修饰静态方法 (锁当前类)**

//进入同步代码前要获得 当前 class 的锁。
synchronized static void method() {
    //业务代码
}

静态 synchronized 方法和非静态 synchronized 方法之间的调用不互斥!如果一个线程 A 调用一个实例对象的非静态 synchronized 方法,而线程 B 需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁

修饰代码块 (锁指定对象/类)
synchronized(object) 表示进入同步代码库前要获得 给定对象的锁。
synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁

synchronized(this) {
    //业务代码
}

总结

  • synchronized关键字修饰静态方法(static修饰的方法)和synchronized(类.class) 代码块都是给Class类上锁
  • synchronized 加到实例方法上面是给对象实例加锁。
  • 不要使用 synchronized(String s) 因为 JVM 中,字符串常量池具有缓存功能。

注意:构造方法不能使用synchronized 修饰 因为构造方法本身就是线程安全的,不存在同步构造方法这一说。

在这里插入图片描述

2.2 synchronized 和 volatile 的区别

synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!

  • volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。但是volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

00## 2.3 synchronized 底层实现
synchronized 底层是通过监视器实现的。监视器的执行流程如下:
在这里插入图片描述

1、 线程通过 CAS(对比并替换)尝试获取锁,如果获取成功,就将 _owner 字段设置为当前线程,说明当前线程已经持有锁,并将 _recursions 重入次数的属性 +1。如果获取失败则先通过自旋 CAS 尝试获取锁,如果还是失败则将当前线程放入到 EntryList 监控队列(阻塞)。
2、当拥有锁的线程执行了 wait 方法之后,线程释放锁,将 owner 变量恢复为 null 状态,同时将该线程放入 WaitSet 待授权队列中等待被唤醒。
3、当调用 notify 方法时,随机唤醒 WaitSet 队列中的某一个线程,当调用 notifyAll 时唤醒所有的 WaitSet 中的线程尝试获取锁。
4、线程执行完释放了锁之后,会唤醒 EntryList 中的所有线程尝试获取锁。

3、Lock

在这里插入图片描述

3.1 Lock 和 synchronized 的区别

在这里插入图片描述

3.2 ReentrantLock 和 synchronized 的区别

相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:
1、等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
2、可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来指定是否是公平的。
3、可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

在这里插入图片描述

4、CAS

CAS 的全称是 Compare And Swap(比较与交换)是一种轻量级的同步操作,也是乐观锁的一种实现,它用于实现多线程环境下的并发算法。CAS 操作包含三个操作数:内存位置(或者说是一个变量的引用)、预期的值和新值。如果内存位置的值和预期值相等,那么处理器会自动将该位置的值更新为新值,否则不进行任何操作。在多线程环境中,CAS 可以实现非阻塞算法,避免了使用锁所带来的上下文切换、调度延迟、死锁等问题,因此被广泛应用于并发编程中

4.1 CAS 执行流程

CAS 执行的具体流程如下:
1、将需要修改的值从主内存中读入本地线程缓存(工作内存);
2、执行 CAS 操作,将本地线程缓存中的值与主内存中的值进行比较;
3、如果本地线程缓存中的值与主内存中的值相等,则将需要修改的值在本地线程缓存中修改;
4、如果修改成功,将修改后的值写入主内存,并返回修改结果;如果失败,则返回当前主内存中的值;
5、在多线程并发执行的情况下,如果多个线程同时执行 CAS 操作,只有一个线程的 CAS 操作会成功,其他线程的 CAS 操作都会失败,这也是 CAS 的原子性保证。

4.2 ABA问题

所谓的 ABA 问题是指在并发编程中,如果一个变量初次读取的时候是 A 值,它的值被改成了 B,然后又其他线程把 B 值改成了 A,而另一个早期线程在对比值时会误以为此值没有发生改变,但其实已经发生变化了,这就是 ABA 问题。

在这里插入图片描述
解决ABA 问题就是通过带版本号的 CAS来解决、
解决 ABA 问题的一种方法是使用带版本号的 CAS,也称为双重 CAS(Double CAS)或者版本号 CAS。具体来说,每次进行 CAS 操作时,不仅需要比较要修改的内存地址的值与期望的值是否相等,还需要比较这个内存地址的版本号是否与期望的版本号相等。如果相等,才进行修改操作。这样,在修改后的值后面追加上一个版本号,即使变量的值从 A 变成了 B 再变成了 A,版本号也会发生变化,从而避免了误判。使用 AtomicStampedReference 来解决 ABA 问题

5、AQS(AbstractQueuedSynchronizer :用于实现各种同步器的抽象类)

AQS(AbstractQueuedSynchronizer)是一个用于实现各种同步器的抽象类,是 JUC(java.util.concurrent)并发包中的核心类之一,JUC 中的许多并发工具类和接口都是基于 AQS 实现的。它提供了一种基于队列的、高效的、可扩展的同步机制,是实现锁、信号量、倒计时器等同步器的基础
同步器:同步器指的是用于控制多线程访问共享资源的机制。同步器可以保证在同一时间只有一个线程可以访问共享资源,从而避免了多线程访问共享资源时可能出现的数据竞争和不一致性问题。Java 中的同步器包括 synchronized 关键字、ReentrantLock、Semaphore、CountDownLatch 等。

5.1 核心思想

用双向队列来保存等待锁的线程,同时利用一个 state 变量来表示锁的状态。AQS 的同步器可以分为独占模式和共享模式两种。
独占模式:同一时刻只允许一个线程获取锁,例如ReentrantLock
共享模式:同一时刻允许多个线程同时获取锁,Semaphore、CountDownLatch、CyclicBarrier
在这里插入图片描述

5.2 资源共享模式

AQS 资源共享模式分为两种:
独占模式:AQS 维护了一个同步队列,该队列中保存了所有等待获取锁的线程。当一个线程尝试获取锁时,如果锁已经被其他线程持有,则将该线程加入到同步队列的尾部,并挂起线程,等待锁被释放。当锁被释放时,从同步队列中取出一个线程,使其获取锁,同时将它从队列中移除,唤醒该线程继续执行。独占模式又分为公平锁和非公平锁。
公平锁:按照线程在队列中的排队顺序,先到者先拿到锁;
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的。

共享模式:AQS 维护了一个等待队列和一个共享计数器。共享计数器表示当前允许获取锁的线程数,当一个线程尝试获取锁时,如果当前允许获取锁的线程数已经达到了最大值,则将该线程加入到等待队列中,并挂起线程,等待其他线程释放锁或者共享计数器增加。当锁被释放时,会从等待队列中取出一个线程,使其获取锁,同时将它从队列中移除,唤醒该线程继续执行。

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

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

相关文章

2024最火的情感短视频素材在哪下载?这里有几个素材网站可以下载

在这个看似无边无际的短视频海洋中,我就像那个手握罗盘、脚踏破旧木筏的勇敢航海家,时刻准备着发现新大陆。而我的罗盘,就是那些能够指引我找到珍稀素材的网站。让我带你领略一下我的航海日记吧。九才素材网: 九才素材网提供了丰…

大数据dolphinscheduler 本地容器化安装

Minio 容器安装 docker run -p 9000:9000 -p 9090:9090 --name minio -d -e "MINIO_ACCESS_KEYminioadmin" -e "MINIO_SECRET_KEYminioadmin" -v D:\SF\DOCKER\minio\data:/data -v D:\SF\DOCKER\minio\config:/root/.minio minio/minio server /da…

全国电影(广州)交易会暨第25届全国优秀影片推介会盛大召开

4月10日,全国电影(广州)交易会暨第25届全国优秀影片推介会在广州增城召开。作为2024年第一场电影全行业会议,该会议受到行业内外广泛关注和响应,被视为新一年度电影市场“晴雨表”和“风向标”。会议以“聚合电影行业内…

利用Python的Pyttsx3库实现离线文字转语音(TTS)功能

项目中需要一个语言模块,之前用微软的TTS其实挺好用的,但是不开源,离线也不好整,打算找个轻量化的,然后发现,Python里Pyttsx3库已经能完美实现我的需求了 Pyttsx3 Pyttsx3是一个Python库,用于…

【面试题】细说mysql中的各种锁

前言 作为一名IT从业人员,无论你是开发,测试还是运维,在面试的过程中,我们经常会被数据库,数据库中最经常被问到就是MySql。当面试官问MySql的时候经常会问道一个问题,”MySQL中有哪些锁?“当我…

简述Kafka的高可靠性

什么叫可靠性? 大家都知道,系统架构有三高:「高性能、高并发和高可用」,三者的重要性不言而喻。 对于任意系统,想要同时满足三高都是一件非常困难的事情,大型业务系统或者传统中间件都会搭建复杂的架构来…

day05-java面向对象(上)

5.1 面向对象编程 5.1.1 类和对象 1、什么是类 类是一类具有相同特性的事物的抽象描述,是一组相关属性和行为的集合。 属性:就是该事物的状态信息。 行为:就是在你这个程序中,该状态信息要做什么操作,或者基于事物…

如何在公网环境远程管理内网Windows系统部署的MongoDB数据库

文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 前言 MongoDB是一个基于分布式文件存储的数…

品牌百度百科词条创建多少钱?

百度百科作为国内最具权威和影响力的知识型平台,吸引了无数品牌和企业争相入驻。一个品牌的百度百科词条,不仅是对品牌形象的一种提升,更是增加品牌曝光度、提高品牌知名度的重要途径。品牌百度百科词条创建多少钱,这成为了许多企…

ubuntu 20.04 设置国内镜像源(阿里源、清华源)

在网上搜了好多设置国内镜像源,都写的乱七八糟的,都是随便换,最后还是换得一堆问题。 镜像源也是跟版本一一对应的,不能随便一个国内源就还过去用,否则会出现各种各样的问题,我也是吃过亏之后才发现的。 国…

单例19c RMAN数据迁移方案

一、环境说明 源库 目标库 IP 192.168.37.200 192.168.37.202 系统版本 RedHat 7.9 RedHat 7.9 数据库版本 19.3.0.0.0 19.3.0.0.0 SID beg beg hostname beg rman 数据量 1353M 说明:源库已经创建数据库实例,并且存在用户kk和他创建的表空间…

Rancher1.0版本安装使用

Rancher1.0版本安装使用 准备环境 rancher是一个容器管理和服务编排工具。 如下链接,讲的可以,大家参考下,我已经测试过了。 http://tonybai.com/2016/04/14/an-introduction-about-rancher/ 操作系统Centos7.2 用户为root。最好使用Cent…

ELK日志分析系统之Zookeeper

一、Zookeeper简介 ZooKeeper是一种为分布式应用所设计的高可用、高性能且一致的开源协调服务,它提供了一项基本服务:分布式锁服务。分布式应用可以基于它实现更高级的服务,实现诸如同步服务、配置维护和集群管理或者命名的服务。 Zookeepe…

SQL注入---HTTP报头注入

文章目录 目录 文章目录 一.uagent注入 二.refeer注入 三.Cookie注入 前文中提到万能密钥的工作原理,然而万能密钥仅在源代码中没有代码审计,此时才被称之为万能密钥,而代码中有代码审计时需要分以下几种情况讨论 一.uagent注入 先看代码&a…

【攻防世界】题目名称-文件包含

看到 include(),想到文件包含,用php伪协议。 知识点 看到 include(),require(),include_once(),require_once() ,想到文件包含,用php伪协议 ?filenamephp://filter/readconvert.base64-encode/…

TinyEMU源码分析之中断处理

TinyEMU源码分析之中断处理 1 触发中断2 查询中断2.1 查询中断使能与pending状态(mie和mip)2.2 查询中断总开关与委托(mstatus和mideleg)2.2.1 M模式2.2.2 S模式2.2.3 U模式 3 处理中断3.1 获取中断编号3.2 检查委托3.3 进入中断3…

【图像处理】-小议YUV色彩空间-YUV和RGB格式的来由,相互关系以及转换方式,并对编程实现的YUV转为RGB程序进行介绍

小议YUV色彩空间 摘要: 在视频图像处理等相关相关领域,YUV是一个经常出现的格式。本文主要以图解的资料形式详细描述YUV和RGB格式的来由,相互关系以及转换方式,并对编程实现的YUV转为RGB程序进行介绍。 1 引言 自然界的颜色千变万化&#xff…

AI算力报告:算力大时代,AI算力产业链全景梳理

今天分享的是AI算力专题系列深度研究报告:《算力大时代,AI算力产业链全景梳理》。 (报告出品方:中信建投证券) 报告共计:98页 核心观点 生成式 AI取得突破,我们对生成式 A 带来的算力需求做…

AI预测体彩排3第1弹【2024年4月12日预测--第1套算法开始计算第1次测试】

前面经过多个模型几十次对福彩3D的预测,积累了一定的经验,摸索了一些稳定的规律,有很多彩友让我也出一下排列3的预测结果,我认为目前时机已成熟,且由于福彩3D和体彩排列3的玩法完全一样,我认为3D的规律和模…

6.基础乐理-升降号、黑键的音名

首先需要先了解音乐中的两个符号,升号和降号,升号的符号像#,降号的符号像b,如下图: 升号表示升高到相邻的音,降号表示降低到相邻的音,现在首先要知道音是有高低的,在钢琴键盘上从左到…