悲观锁与乐观锁(CAS实现)

2023-05-16

CAS乐观锁-悲观锁

悲观锁与乐观锁

  • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 阻塞 直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语 synchronized 关键字的实现也是悲观锁。
  • 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会 判断一下在此期间别人有没有去更新这个数据 ,可以使用版本号等机制。乐观锁适用于 多读 的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。

锁存在的问题

​ Java在 JDK1.5 之前都是靠 synchronized 关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。这就是一种独占锁,独占锁其实就是一种悲观锁,所以可以说 synchronized 是悲观锁。

存在问题:

  1. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
  2. 一个线程持有锁会导致其它所有需要此锁的线程挂起。
  3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

乐观锁

​ 乐观锁( Optimistic Locking )其实就是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。
  上面提到的乐观锁的概念中其实已经阐述了它的具体实现细节:主要就是两个步骤:**冲突检测和数据更新。**其实现方式有一种比较典型的就是 CAS。

CAS - CompareAndSwap

比较设置,方法 CompareAndSet(num,num+1);

比较如果是 num ,就设置成 num+1;

​ 以 java.util.concurrent 中的 AtomicInteger 为例,看一下在不使用锁的情况下是如何保证线程安全的。主要理解 getAndIncrement 方法,该方法的作用相当于 ++i 操作。

public class AtomicInteger extends Number implements java.io.Serializable {  
    private volatile int value; 

    public final int get() {  
        return value;  
    }  

    public final int getAndIncrement() {  
        for (;;) {  
            int current = get();  
            int next = current + 1;  
            if (compareAndSet(current, next))  
                return current;  
        }  
    }  

    public final boolean compareAndSet(int expect, int update) {  
        // 利用JNI(Java Native Interface)来完成CPU指令的操作
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
    }  
}

CAS的缺点:

1.CPU开销较大

​ 自旋CAS(不成功,就一直循环执行,直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的 pause 指令那么效率会有一定的提升,pause 指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

2.不能保证代码块的原子性

​ CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。

什么是CAS机制

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

这样说或许有些抽象,我们来看一个例子:

1.在内存地址V当中,存储着值为10的变量。

img

2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。

img

3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

img

4.线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

img

5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。

img

6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。

img

7.线程1进行SWAP,把地址V的值替换为B,也就是12。

img

CAS 存在的问题

ABA问题

​  比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但可能存在潜藏的问题。如下所示:

img

​ 现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:
          head.compareAndSet(A,B);
  在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态:

img

​ 此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为:

img

​ 其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。

CAS与Synchronized

​ 1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。

2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

Concurrent包的实现

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

1. A线程写volatile变量,随后B线程读这个volatile变量。

2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。

3. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。

4. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。

​ Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

1. 首先,声明共享变量为volatile;

2. 然后,使用CAS的原子条件更新来实现线程之间的同步;

3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

img

JVM中的CAS(堆中对象的分配):

​ Java调用new object()会创建一个对象,这个对象会被分配到JVM的堆中。那么这个对象到底是怎么在堆中保存的呢?

​ 首先,new object()执行的时候,这个对象需要多大的空间,其实是已经确定的,因为java中的各种数据类型,占用多大的空间都是固定的(对其原理不清楚的请自行Google)。那么接下来的工作就是在堆中找出那么一块空间用于存放这个对象。
  在单线程的情况下,一般有两种分配策略:

1. 指针碰撞:这种一般适用于内存是绝对规整的(内存是否规整取决于内存回收策略),分配空间的工作只是将指针像空闲内存一侧移动对象大小的距离即可。

2. 空闲列表:这种适用于内存非规整的情况,这种情况下JVM会维护一个内存列表,记录哪些内存区域是空闲的,大小是多少。给对象分配空间的时候去空闲列表里查询到合适的区域然后进行分配即可。

但是JVM不可能一直在单线程状态下运行,那样效率太差了。由于再给一个对象分配内存的时候不是原子性的操作,至少需要以下几步:查找空闲列表、分配内存、修改空闲列表等等,这是不安全的。解决并发时的安全问题也有两种策略:

1. CAS:实际上虚拟机采用CAS配合上失败重试的方式保证更新操作的原子性,原理和上面讲的一样
  2. TLAB:如果使用CAS其实对性能还是会有影响的,所以JVM又提出了一种更高级的优化策略:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲区(TLAB),线程内部需要分配内存时直接在TLAB上分配就行,避免了线程冲突。只有当缓冲区的内存用光需要重新分配内存的时候才会进行CAS操作分配更大的内存空间。
机是否使用TLAB,可以通过-XX:+/-UseTLAB参数来进行配置(jdk5及以后的版本默认是启用TLAB的)。

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

悲观锁与乐观锁(CAS实现) 的相关文章

  • CAS 5.2.X 使用cas-overlay-template 搭建cas-server

    1 下载 cas overlay template 地址 xff1a https github com apereo cas overlay template 选择需要的版本 xff1a cas 6 x开始使用gradle构建 xff0c
  • shiro-cas------本地配置cas为HTTPS登录

    上一篇 xff1a shiro cas 搭建基础cas服务器 解决上图所提示的问题 xff08 在本地 xff09 xff0c 需要配置https请求 首先给这个服务起个域名 xff1a shiro sso com 配置到本地的host文件
  • Linux中CAS服务端部署

    将cas war 部署进tomcat war包放到webapps下面 cas WEB INF deployerConfigContext xml目录下 可查看修改默认密码casuser Mellon lt bean id 61 34 pri
  • cas-overlay-template 搭建单点登录服务端

    1 先下载overlay template的源码 git clone https github com apereo cas overlay template git 切换到5 3的分支 2 编译的坑 需要下载cas server weba
  • 锁与CAS详解

    一 悲观锁与乐观锁 乐观锁和悲观锁问题 xff0c 是出现频率比较高的面试题 本文将由浅入深 xff0c 逐步介绍它们的基本概念 实现方式 含实例 适用场景 xff0c 以及可能遇到的面试官追问 xff0c 希望能够帮助你打动面试官 乐观锁
  • shiro-cas------自定义登录页面

    我的自定义登录页 xff08 需要登录页面的 xff0c 推荐给你们一个登陆页面地址 xff09 我的项目结构 xff1a 学习过程参考官方文档https apereo github io cas 5 3 x installation Us
  • ABA问题基础及解决

    一 ABA问题的产生 二 AtomicReference原子引用 三 AtomicStampedReference版本号原子引用 四 ABA问题解决 前面的内容之间的关联 CAS gt Unsafe类 gt CAS思想 gt ABA问题 g
  • CAS5.3 服务器集成MySQL8数据库

    MySQL 相关准备 1 安装MySQL8 并安装相关数据库实例 省略一千字 2 新建ucas auth user表 并增加相关用户条记录 DROP TABLE IF EXISTS ucas auth user CREATE TABLE u
  • 【Python开发】Flask中的单点登录解决方案

    Flask中的单点登录解决方案 1 SSO 和 CAS 单点登录 Single Sign On SSO 就是通过用户的一次性鉴别登录 当用户在身份认证服务器上登录一次以后 即可获得访问单点登录系统中其他关联系统和应用软件的权限 同时这种实现
  • cas5.3.2单点登录-Cas Server开启Oauth2.0协议(二十)

    原文地址 转载请注明出处 https blog csdn net qq 34021712 article details 82290876 王赛超 学习Cas这么久了 一直在按照CAS自身的协议接入 Cas的强大在于有官方的插件 可以支持其
  • 面试必问的 CAS ,要多了解

    前言 CAS Compare and Swap 即比较并替换 实现并发算法时常用到的一种技术 Doug lea大神在java同步器中大量使用了CAS技术 鬼斧神工的实现了多线程执行的安全性 CAS的思想很简单 三个参数 一个当前内存值V 旧
  • 如何在Spring的CAS服务属性中正确设置服务URL

    当使用 Spring Security CAS 时 我总是遇到发送到 CAS 的回调 URL 即服务属性 的小障碍 我看过很多例子 例如this and this但它们都使用硬编码的 URL 甚至Spring 的 CAS 文档 典型的剪辑看
  • CAS AD LDAP 32 错误

    当我尝试使用 CAS 登录时 我看到了这一点 CAS 通过 LDAP 对 AD 进行身份验证 SEVERE Servlet service for servlet cas threw exception javax naming NameN
  • 由 CAS 引起的 APEX 应用程序生成器的编码问题

    我有一段时间对 APEX 应用程序生成器 sql 研讨会有疑问 存在编码问题 APEX 必须提供东欧字符 最后我找到了问题的根源 我已经通过 web xml 将 CAS sso 实现到 APEX 中 APEX 位于 Tomcat ORDS
  • 在 CAS 中,如果尚未接受协议,如何限制仅访问一项服务?

    有一个要求 用户应该首先接受一些许可协议 存在仅在服务中 以便能够登录所有其他服务 所以流程应该是 用户通过CAS登录 因为他还没有接受协议 所以只能登录serviceS 当他接受serviceA上的协议后 他也可以登录其他服务 Notes
  • 将属性从 CAS 释放到 Spring security

    我在客户端使用 Spring security 3 X 在服务器上使用 CAS 4 0 当我进行 CAS Spring 安全集成时 我能够达到票证验证成功的水平 并能够在客户端获得适当的角色 但我在 casServiceValidation
  • 浏览器不遵循 AJAX 响应的重定向(PHP 生成的响应使用 CAS 身份验证)

    好吧 看来我最初的问题犯了一个错误 因此 这里有一些更正 答案仍然适用 因为当协议更改为 HTTPS SSL 时 第二个重定向就会停止 就我而言 重定向发生了多次 并且浏览器不遵循第二次重定向 遵循第一个重定向 但返回错误 我一直读到包含重
  • 使用 Spring Security 和 CAS 单点注销

    使用纯 Spring Java 配置 我在让 Spring 和 CAS 执行单点登录时遇到问题 我使用以下配置进行单点登录 我使用一个简单的 JSP 页面对 url 进行表单 POSThttps nginx shane com app lo
  • 使用 django-cas-ng 在管理站点上进行身份验证

    我在用着Django Cas NG https github com mingchen django cas ng用于验证用户身份的框架 主要问题是管理页面仍然使用默认的登录视图 到目前为止使用的方法 1 使用环境变量 来自文档 CAS A
  • CAS 注销和 cookie 消除

    我刚刚制作了一个 HelloWorld servlet 并在其上实现了 CAS 我能够毫无问题地登录 并且 CAS 在我的浏览器中设置 3 个 cookie CASGT 并为 cas 设置 2 个 JSESSIONID 1 另一个为 hel

随机推荐

  • excel数据对比-----查找两列(表)的相同数据

    原创作品 xff0c 允许转载 xff0c 转载时请务必以超链接形式标明文章 原始出处 作者信息和本声明 否则将追究法律责任 http xueli blog 51cto com 3325186 920592 现有两个excel表 xff0c
  • discuz 微社区 您请求的XXXX无法访问 接口错误(ERR02)

    我遇到的情况 xff1a 1 UC可以访问页面 xff0c 用微信报错 2 4G网络下可以访问 xff0c WiFi网络下报错 网上有两种解决方法 xff1a 1 关闭防采集 xff0c 我最终的采用方法 2 default下的mobile
  • 所有文件夹都变成1KB文件夹快捷方式病毒的手动清除方法

    电脑差不多都因使用U盘而感染了病毒 xff0c 其中一个就是Autoran病毒的变种 xff0c 它的症状我就不再描述了 xff0c 另外一个病毒的症状是所有文件夹都变成了1KB文件夹快捷方式 xff0c 各盘无法双击打开 xff08 但右
  • 搜狗高速浏览器2.0使用体验

    2010年 4 月 8 号 xff0c 我们终于迎来了 国内浏览器的后起之秀搜狗高速浏览器2 0 正式版 的 发布 高速真双核引擎 的概念得到了落实 它新增并改进了诸多功能 xff0c 修改了一些bug xff0c 从整体提高 搜狗高速浏览
  • Connection refused错误

    这个问题整了我两天时间 xff0c 现在终于解决了 问题 xff1a 用php 构造http请求访问自身web服务器页面 xff0c 总是报Connection refused 111 错误 显示 xff1a unable to conne
  • QT样式表从入门到精通

    QT样式表从入门到精通 文章目录 QT样式表从入门到精通前言1 背景介绍2 初级学习2 1 34 盒子 34 模型2 2 语法说明2 3 基础控件2 4 控件状态表2 5 选择器 3 中级学习3 1 坐标讲解3 1 1 相对坐标3 1 2
  • GIF89a图片头文件欺骗

    1 什么是GIF89a 一个GIF89a图形文件就是一个根据图形交换格式 xff08 GIF xff09 89a版 xff08 1989年7 月发行 xff09 进行格式化之后的图形 在GIF89a之前还有87a版 xff08 1987年5
  • txt文件导入mysql

    LOAD DATA LOW PRIORITY CONCURRENT LOCAL INFILE 39 file name 39 REPLACE IGNORE INTO TABLE tbl name CHARACTER SET charset
  • mac下终端无法使用数字小键盘的解决方案

    终端下 偏好设置 xff0d 高级 xff0d xff08 去掉 xff09 允许VT100应用程序小键盘模式
  • Mac Eclipse Failed to load JavaHL Library.

    转自 xff1a http blog csdn net wy10207010219 article details 42294293 写这一篇前我想发表一下感慨 xff1a 你所害怕的事 xff0c 你想要逃避的事 xff0c 在将来的某个
  • ROS学习笔记(一)ROS安装和helloworld

    ROS学习笔记 xff08 一 xff09 ROS安装和helloworld 文章目录 一 ros安装及测试1 打开ubuntu软件和更新 xff0c 进行如下设置2 设置安装源3 设置安装密钥4 更新软件源5 安装ros6 添加命令7 初
  • 使用ActiveMQ进行C++与C#的通信4 - 使用C++连接ActiveMQ

    在上一节编译ActiveMQ CPP的基础上 xff0c 创建C 43 43 控制台应用程序 xff0c 将activemq cpp项目中的include文件夹拷贝到该C 43 43 项目中 xff0c 设置好附加包含目录 将生成好的lib
  • 使用ActiveMQ进行C++与C#的通信5 - 实现C++和C#的通信

    在前几篇文章分别实现C C 43 43 连接ActiveMQ的基础上 xff0c 本文介绍如何使它们通信 使不同的进程对同一个ActiveMQ消息队列进行访问 xff0c 就能够达到消息互通的效果 例如使用queue test1 log作为
  • 【计算机游戏开发】游戏交互界面设计

    github项目地址 一 实验目的与要求 熟悉交互界面设计原理 了解Cocos2d x中的用户交互 触摸事件 碰撞检测机制 二 实验内容与方法 完成游戏编译 50分 仿照实验一 英雄快跑 实验 xff0c 将教材源码和素材文件复制到自己的项
  • k-近邻实现手写数字识别

    1 k 近邻工作原理 简单地说 xff0c K近邻算法采用测量不同特征值之间的距离方法进行分类 该算法具有一下特点 优点 xff1a 精度高 对异常值不敏感 无数据输入假定 缺点 xff1a 计算复杂度高 空间复杂度高 K近邻算法的工作原理
  • selenium之CSS定位

    一 层级定位 1 xff1a 所有标签 2 标签名 xff1a 查找所有该标签名 3 标签名 xff0c 标签名 xff1a 查找多个标签名 id用 表示 索引尽量使用xpath 二 三大等待和切换 1 页面元素可以定位 xff0c 但是代
  • STM32核心笔记

    STM32核心笔记 文章目录 STM32核心笔记1 下载程序的两种方式1 1 串口ISP1 2 仿真器1 2 1 J Link1 2 1 ST LINK 2 认识寄存器3 地址总线与外设地址映射关系4 GPIO的三种配置5 时钟树6 常用中
  • 用efibootmgr管理UEFI启动项,添加丢失的启动项

    UEFI用来替代传统BIOS引导操作系统 xff0c 学会修改UEFI启动项也变得十分重要 xff0c UEFI全称为 xff1a 统一的可扩展固件接口 xff08 Unified Extensible Firmware Interface
  • JAVA-生产消费者模型

    简单介绍一下生产消费者模型 xff0c 如下图 xff1a 一般来说 xff0c 生产消费者模型就是消费者和生产者可以共同操作茶叶仓库这一个共享资源 打一个比方 xff0c 生产者生产茶叶供向茶叶市场 xff0c 消费者在茶叶市场购买茶叶
  • 悲观锁与乐观锁(CAS实现)

    CAS乐观锁 悲观锁 悲观锁与乐观锁 悲观锁 xff1a 总是假设最坏的情况 xff0c 每次去拿数据的时候都认为别人会修改 xff0c 所以每次在拿数据的时候都会上锁 xff0c 这样别人想拿这个数据就会 阻塞 直到它拿到锁 传统的关系型