乐观锁与悲观锁讲解,CAS、synchronized、锁升级、ReentrantLock、AQS

2023-11-09

什么是乐观锁和悲观锁

乐观锁:
总是假设最好的情况,每次拿数据的是时候都认为别人没有进行修改,所以不会加锁,但是为了保证线程安全,每次修改的时候都会判断这个数据有没有被修改过,适用于写少的场景,因为在写操作较多时如果失败会不断通过自旋判断数据有没有被修改,十分消耗CPU资源,CAS是实现乐观锁的一种方式,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

悲观锁:
总是假设最坏的情况,每次拿数据的时候都认为别人会修改数据,所以每次都要加锁,这样别人在拿这个数据的时候都会被阻塞,直到获取到锁 (共享资源每次只能由一个线程使用,使用的时候会加锁,别的线程会被阻塞,直到该线程释放锁后去尝试获取锁)适用于写多的场景,资源开销较小,Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁的实现

CAS

CAS是实现乐观锁的一种方式,即compare and swap(比较与交换),涉及
三个操作数:

  • 需要读写的内存值 V
  • 进行比较的值 A
  • 拟写入的新值 B

会先把要原值A查询出来,然后只有当V=A时才会去写入新值B,如果不相等则说明原值已被别的线程修改过了,就会去不断自旋重试再次修改值

CAS的缺点

1、ABA问题

简单来说就是,当一个线程使用CAS时发现V=A,就一定说明这个值没有被别的线程修改过吗?不一定,因为可能其他线程修改了以后又被修改回来了,但是CAS还是会误认为这个值没有被修改过,这就是ABA问题。

  1. 线程1读取了数据A
  2. 线程2读取了数据A
  3. 线程2通过CAS比较,发现值是A没错,可以把数据A改成数据B
  4. 线程3读取了数据B
  5. 线程3通过CAS比较,发现数据是B没错,可以把数据B改成了数据A
  6. 线程1通过CAS比较,发现数据还是A没变,就写成了自己要改的值

那么要如何解决ABA问题呢?

加标志位,比如加一个自增的字段,在线程修改值的时候,这个值的自增标志位也加1,这时就算再把值修改回来后,标志位也不是一开始的那个了,在CAS的时候也判断一下标志位是否和一开始的标志位相等,这样就可以保证这个值一定是没有被其他线程修改过了
JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

2、CPU开销大

如果CAS长时间都没有成功,就会一直自旋,给CPU带来了大量的执行开销

3、只能保证一个共享变量的原子操作

CAS只能保证单个共享变量的原子性
但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

悲观锁的实现

synchronized

synchronized是悲观锁的一种实现方式,synchronized的三种形式:

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态同步方法,锁是当前类的 Class 对象。
  • 对于同步方法块,锁是 Synchonized 括号里配置的对象。

synchronized 用的锁是存在 Java 对象头里的。如果对象是数组类型,则虚拟机用 3个字宽(Word)存储对象头,如果对象是非数组类型,则用 2 字宽存储对象头

对象头的结构:
在这里插入图片描述
在这里插入图片描述
32位虚拟机的Mark Word状态变化:
在这里插入图片描述
64位虚拟机的Mark Word状态变化:

在这里插入图片描述

锁升级在这里插入图片描述

在这里插入图片描述
偏向锁:
当一个线程访问同步快并获得锁的时候,会在对象头和栈帧的锁中记录偏向的线程ID,以后每次同一个线程访问这个同步块的时候就不需要加锁和解锁了,只需要看看对象头是否存储这个线程的偏向锁即可
偏向锁的撤销:
当其他线程想要竞争偏向锁的时候,会先暂停拥有偏向锁的线程并检查是否还活着

  • 如果没有活着,就把对象头设置成无锁状态,锁不升级,还是偏向锁,使用CAS替换偏向锁线程ID为另一个线程
  • 如果还活着,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

偏向锁的关闭:
-XX:- UseBiasedLocking=false,关闭偏向锁,程序默认会进入轻量级锁状态
轻量级锁:
当多个线程访问同步块的时候,会竞争锁,当有一个线程竞争到锁的时候,锁被替换为轻量级锁,其他线程通过自旋的方式不断获取锁,当自旋次数过大的时候,轻量级锁会升级为重量级锁,此时所有没有竞争到锁的线程会进入到阻塞队列中,锁被释放之后就会唤醒阻塞队列中的所有线程,并开始重新竞争锁
轻量级锁解锁:
轻量级解锁时,会使用原子的 CAS 操作将 Displaced Mark Word 替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。因为自旋很消耗cpu,所以当升级为重量级锁的时候就不会再恢复成轻量级锁了
重量级锁:
轻量级锁自旋次数过大时会升级为重量级锁,此时没有竞争到锁的线程会进入阻塞队列,等锁释放之后cpu会唤醒阻塞队列中的所有线程,重新竞争锁
锁的优缺点对比:
在这里插入图片描述

ReentrantLock

ReentrantLock可重入锁,在jdk1.7的ConcurrentHashMap中,就是由ReentrantLock实现的。

AQS

AQS是一个队列同步器,同步队列是一个双向链表,有一个状态标志位state,如果state为1的时候,表示有线程占用,其他线程会进入同步队列等待,如果有的线程需要等待一个条件,会进入等待队列,当满足这个条件的时候才进入同步队列,ReentrantLock就是基于AQS实现的
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

乐观锁与悲观锁讲解,CAS、synchronized、锁升级、ReentrantLock、AQS 的相关文章

  • C++11多线程(三) lock_guard unique_lock

    文章目录 C 11多线程 三 lock guard unique lock 导读 Lock guard 示例代码 lock guard lt gt 的第二个参数 unique lock unique lock源码浅析 部分 unique l
  • java中synchronized关键字

    1 synchronized关键字简介 synchronized是java中的一个关键字 在中文中为同步 也被称之为 同步锁 以此来达到多线程并发访问时候的并发安全问题 可以用来修饰代码块 非静态方法 静态方法等 修饰代码块时 给当前指定的
  • futureTask RunnableFuture Future 三者关系认知

    对于这三者首先我们看下源码 之后在分别写几个demo讲解下用法 public interface RunnableFuture
  • qt多线程使用方式

    有5个方式 可以参考这个博客 Qt 中开启线程的五种方式 qt 线程 lucky billy的博客 CSDN博客 注 为了实现更加灵活的线程管理 因为这5种都有一些不方便之处 QThread需要子类化且不能传参 moveToThread不能
  • JUC编程

    1 JUC JUC就是java util concurrent工具包的简称 这是一个处理线程的工具包 JDK 1 5开始出现的 1 传统的synchronized public class Synchronized public stati
  • Disruptor 详解

    Disruptor 详解 想了解一个项目 最好的办法就是 把它的源码搞到本地自己捣鼓 在网上看了 N 多人对 Disruptor 速度的吹捧 M 多人对它的机制分析 就连 Disruptor 官方文档中 也 NB 哄哄自诩 At LMAX
  • 如何在spring框架中解决多数据源的问题

    在我们的项目中遇到这样一个问题 我们的项目需要连接多个数据库 而且不同的客户在每次访问中根据需要会去访问不同的数据库 我们以往在 spring 和 hibernate 框架中总是配置一个数据源 因而 sessionFactory 的 dat
  • 多线程实现事务回滚

    多线程实现事务回滚 特别说明CountDownLatch CountDownLatch的用法 CountDownLatch num 简单说明 主线程 mainThreadLatch await 和mainThreadLatch countD
  • volatile和synchronized的区别

    共性 volatile与synchronized都用于保证多线程中数据的安全 区别 1 volatile修饰的变量 jvm每次都从主存 主内存 中读取 而不会从寄存器 工作内存 中读取 而synchronized则是锁住当前变量 同一时刻只
  • Java使用多线程导入数据到Oracle中

    Oracle中的设置 多线程导入数据到Oracle中 如果是自己设置主键的值 那么肯定会遇到主键冲突的问题 例如线程A计算出的id为10 max id 1 在A线程还没有完成导入时线程B用相同办法得到的id也是10 这时两个线程都请求插入数
  • cpu的出错概率?

    我今天想到了一个很不懂的问题 cpu执行指令会出错吗 出错的概率是多少 为什么服务器能够不间断的工作很长时间呢 难道cpu指令级的东西不会出错 操作系统怎么避免这些错误呢 2012 5 27 找到一篇文章 http wuyudong blo
  • 关于Semaphore信号量的源码解读

    Semaphore的简单使用 利用Semaphore可以实现对线程数量的控制 比如如下的代码 class SemaphoreTest public static void main String args Semaphore semapho
  • JAVA使用线程池查询大批量数据

    前言 在开发过程中可能会碰到某些独特的业务 比如查询全部表数据 数据量过多会导致查询变得十分缓慢 虽然在大多数情况下并不需要查询所有的数据 而是通过分页或缓存的形式去减少或者避免这个问题 但是仍然存在需要这样的场景 比如需要导出所有的数据到
  • python网络爬虫实战——实时抓取西刺免费代理ip

    参考网上高手示例程序 利用了多线程技术 Python版本为2 7 coding utf8 import urllib2 import re import threading import time rawProxyList checkedP
  • QT实现多线程,以及子线程调用主线程方法与变量

    实现思路 第一步需要将子线程声明为主线程的友元类 第二步是将主线程类对象的地址通过信号槽传递给子线程中创建的对象 使得子线程能访问主线程的数据的 1 子线程 displayresult h 头文件 伪代码 include tabwindow
  • wxwidgets编写多线程程序--wxThread

    细节描述 线程基本上来说是应用程序中一条单独执行的路径 线程有时被称为轻量级进程 但线程与进程的根本不同之处在于不同进程存储空间是相互独立的 而同一进程里的所有线程共享同一地址空间 尽管这使得它更容易共享几个线程间的普通数据 但这也使得它有
  • iOS线程初探(四) GCD 和 NSOperation 小结

    参考资料 关于iOS多线程 看我就够了 GCD 在GCD中 有两个概念很重要 那就是任务和队列 任务 其实就是你需要做的事情 一个Block而已 任务有两种执行方式 同步执行和异步执行 同步执行 会阻塞当前线程 直至该任务执行完成后当前线程
  • 由一个多线程共享Integer类变量问题引起的。。。

    假设并发环境下 业务代码中存在一些统计操作 为了保证线程安全 开发人员往往会对计数值进行加锁 synchronized 值得注意的是 直接对Integer类型进行加锁 似乎并不会达到预期效果 比如下面这段代码 Integer num new
  • 多线程编程与性能优化

    引言 在上一篇的入门篇中 我们对Android线程的基础概念和多线程编程模型有了初步了解 本篇将深入探讨多线程编程技术和性能优化策略 以提升应用的效率和响应性 高级多线程编程技术 使用线程池管理线程 线程池是一组预先创建的线程 用于执行任务
  • TaskDecatator用法

    在Spring框架中 TaskDecorator 是一个接口 它可以用来自定义由 ThreadPoolTaskExecutor 或其他任务执行器管理的任务的装饰行为 这通常用于在执行任务之前和之后添加某些上下文相关的行为 比如设置线程上下文

随机推荐

  • LeetCode 面试题01.09 字符串轮转

    题目 字符串轮转 给定两个字符串s1和s2 请编写代码检查s2是否为s1旋转而成 比如 waterbottle 是 erbottlewat 旋转后的字符串 示例1 输入 s1 waterbottle s2 erbottlewat 输出 Tr
  • 一个独特的开源插件evil.js

    前言 最近发现一个好玩有解压的开源插件 注意 不可使用在正式项目中 这里分享下 gitee地址 evil js 此代码仅在周日的时候执行以下逻辑 声明 请勿用于任何项目 如果导致任何问题 与本人无关https gitee com haoxi
  • 矩阵LU分解

    一 矩阵LU分解定理 设A为n阶矩阵 如果A的顺序主子式Di 0 i 1 2 n 1 则A可以分解为一个单位下三角矩阵L和一个上三角矩阵U的乘积 且这种分解是唯一的 即A LU 二 矩阵LU分解Python代码 自己原创 def lu de
  • 第十二章 - 条件判断(case when 和 if)和视图

    第十二章 条件判断 case when 和 if 和视图 view if 的用法 case when 的用法 视图 view 的用法 if 的用法 通过使用if函数可以实现数据二分类或者多分类的功能 比如按年龄区分青年 中年 老年 或者按价
  • Python2_Pandas库(数据读取)

    1 数据读取 food info csv数据 import pandas food info pandas read csv food info csv read csv函数读取csv数据文件 print type food info Da
  • 汇编笔记——判断大小

    判断指令 CMP AL num 判断条件 这里的JA JB JE JMP相当于goto命令 JA L0 A gt above AL比num大 执行L0 JB L1 B gt below AL比num小 执行L1 JE L2 E gt equ
  • 树结构转List

    使用LinkedList效率更高 1 单个顶级节点 public static List
  • 网络安全(黑客技术)自学笔记

    目录 一 自学网络安全学习的误区和陷阱 二 学习网络安全的一些前期准备 三 网络安全学习路线 四 学习资料的推荐 想自学网络安全 黑客技术 首先你得了解什么是网络安全 什么是黑客 网络安全可以基于攻击和防御视角来分类 我们经常听到的 红队
  • chromium之jumplist

    chrome在win7及之后系统添加jumplist功能 jumplist即系统任务栏相关的功能 包括任务栏图标 鼠标放置后视图 进度条 右键菜单等等 路径 chromium src chrome browser win jumplist
  • 21. 合并两个有序链表

    21 合并两个有序链表 简单 将两个升序链表合并为一个新的 升序 链表并返回 新链表是通过拼接给定的两个链表的所有节点组成的 输入 l1 1 2 4 l2 1 3 4 输出 1 1 2 3 4 4 示例 2 输入 l1 l2 输出 示例 3
  • vue 阻止事件冒泡,捕获方法

    要想了解 VUE 阻止事件冒泡和捕获方法 首先要了解一下 JS 事件和 JS 阻止事件冒泡 捕获方法 1 js 事件的三阶段 捕获阶段 目标阶段 执行当前对象的事件处理程序 冒泡阶段 2 js 阻止事件冒泡 捕获 阻止事件冒泡 event
  • OceanBase 安全审计之透明加密

    承接前文 OceanBase 安全审计的 传输加密 本文主要实践数据透明加密 并验证加密是否有效 作者 张乾 外星人2号 兼任四位喵星人的铲屎官 爱可生开源社区出品 原创内容未经授权不得随意使用 转载请联系小编并注明来源 本文约 1200
  • layui导入Excel文件

    具体如下图所示 首先 导入layui第三方插件js 地址 https fly layui com extend excel 1 在页面中引入excel js文件 引入excel layui config base layui ext ext
  • NOIP 1998 普及组 复赛 幂次方

    NOIP 1998 普及组 复赛 幂次方 1208 2的幂次方表示 此文代码与本人极其相似 唯一不同就是此文代码成功了 http www cnblogs com bofengyu p 4477355 html 思路 先打印2 7 2 3 2
  • 【死磕 Java 基础】--- 我一口气自己就动手实现一个 LRU

    大家好 我是大明哥 个人网站 https www topjava cn LRU 即 Least Recently Use 直译为 最近最少使用 它是根据数据的历史访问记录来进行数据淘汰的 淘汰掉最先访问的数据 其核心思想是 如果数据最近被访
  • UE4_编辑器UMG关闭窗口不能立刻销毁UMG

    问题描述 运行UMG 使之创建显示到视口 效果如下 点击关闭按钮 再次运行该UMG 使之显示到视口 通过在c 中获取该UMG的数量 发现数量为2 不是所预计得1 导致编写得代码出现bug 修改办法 截图如下 该解决办法不是实用所有程序 因为
  • Linux IPC之内存映射mmap()

    导言 如何使用mmap 系统调用来创建内存映射 它可以用于IPC以及其他很多方面 概述 mmap 系统调用 在调用进程的虚拟地址空间中创建一个新内存映射 映射分为两种 文件映射 内存映射文件 将一个文件的一部分直接映射到调用进程的虚拟内存中
  • C# 文件操作之创建文件夹

    本文章主要是讲述C 中文件操作的基础知识 如何创建文件夹 创建文件 介绍Directory类 DirectoryInfo类和使用FolderBrowserDialog组件 文件夹对话框 文章属于基础知识 参考了书籍刘丽霞 C 范例开发大全
  • SpringBoot不依赖(禁用)Gemfire数据库启动的方法

    默认情况下SpringBoot的启动是要依赖GemFire做为缓存数据库的 如果做一个小的项目 不需要缓存数据库 这将是一个不容易摘除的工作 通过对其官网技术资料的分析 这个也很容易做到 说一下我的技术环境 Spring boot版本 2
  • 乐观锁与悲观锁讲解,CAS、synchronized、锁升级、ReentrantLock、AQS

    什么是乐观锁和悲观锁 乐观锁 总是假设最好的情况 每次拿数据的是时候都认为别人没有进行修改 所以不会加锁 但是为了保证线程安全 每次修改的时候都会判断这个数据有没有被修改过 适用于写少的场景 因为在写操作较多时如果失败会不断通过自旋判断数据