广告
返回顶部
首页 > 资讯 > 后端开发 > Python >详解Java中的悲观锁与乐观锁
  • 331
分享到

详解Java中的悲观锁与乐观锁

2024-04-02 19:04:59 331人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

目录一、悲观锁二、乐观锁三、CAS四、AtomicXXX五、CAS中的ABA问题六、ABA问题解决方案七、使用CAS会引起的问题八、Synchronized锁优化九、偏向锁十、轻量级

一、悲观锁

悲观锁顾名思义是从悲观的角度去思考问题,解决问题。它总是会假设当前情况是最坏的情况,在每次去拿数据的时候,都会认为数据会被别人改变,因此在每次进行拿数据操作的时候都会加锁,如此一来,如果此时有别人也来拿这个数据的时候就会阻塞知道它拿到锁。在Java中,Synchronized和ReentrantLock等独占锁的实现机制就是基于悲观锁思想。在数据库中也经常用到这种锁机制,如行锁,表锁,读写锁等,都是在操作之前先上锁,保证共享资源只能给一个操作(一个线程)使用。

由于悲观锁的频繁加锁,因此导致了一些问题的出现:比如在多线程竞争下,频繁加锁、释放锁导致频繁的上下文切换和调度延时,一个线程持有锁会导致其他线程进入阻塞状态,从而引起性能问题。

二、乐观锁

乐观锁从字面上看是从积极,乐观的角度去看待问题,因此它认为数据一般不会产生冲突,因此一般不加锁,当数据进行提交更新时,才会真正对数据是否产生冲突进行监测。如果发生冲突,就返回给用户错误信息,由用户来决定如何去做,主要有两个步骤:冲突检测和数据更新。

三、CAS

CAS(compare and set),比较和更新。CAS是乐观锁的技术实现,当多个线程尝试使用CAS同时来更新同一个变量,只有一个线程能够更新变量值,而其他的线程都会失败,失败的线程并不会被挂起,告知这次竞争失败,可以再次尝试。

CAS操作包含三个操作数:

  • 需要读写的内存位置(V)
  • 需要比较的预期原值(A)
  • 拟写入的新值(B)

如果内存位置V的值与原预期值A相匹配,那么处理器就会自动将该位置更新为新值B,否则处理器不做任何处理。乐观锁是一种思想,CAS是这种思想的一种实现方法。Java中对CAS支持,在jdk1.5之后新增java.util.concurrent(J.U.C)就是建立CAS基础上,CAS是一种非阻塞的实现,例如:Atomic

四、AtomicXXX

在Java中,提供了一些原子化的操作类型,如下操作


 private volatile int value;
 
public final int get() {
        return value;
    }

读取的值,value是声明为volatile的,就可以保证在没有锁的情况下,线程可见性

在涉及到数据变更,以incrementAndGet实例:++i操作


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

采用的CAS的操作,每次读取内存中的数据,让后将数据+1的结果进行CAS操作,如果成功就返回结果,负责重试指导成功为止,这里调用compareAndSet是CAS所依赖的JNI的实现的乐观锁 。


public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapint(this, valueOffset, expect, update);
    }

Atomic就是volatile的使用场景,也是CAS的使用场景。

五、CAS中的ABA问题

CAS使用起来能够提高性能,但会引起ABA的问题

假如如下事件序列:

1、线程1从内次位置V来获取值A

2、线程2从内存位置V获取A

3、线程2进行一些操作,将B写入到V

4、线程2将A写入位置V

5、线程1进行CAS操作,发现位置V的值任然为A,操作成功了

6、线程1尽管CAS操作成功了,该过程有可能出现问题,对于线程1,线程2做的处理就可能丢失了

举例说明:一个链表ABA的例子

1、现有一个用单向链表实现的堆栈,栈顶为A。这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:

1head.compareAndSet(A,B);

2、在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再依次入栈D、C、A,而对象B此时处于游离状态。

3、此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B。但实际上B.next为null,此时堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,C、D被丢掉了。

image.png

六、ABA问题解决方案

ABA问题解决思路就是使用版本号,在变量前面追加版本号,每次对变量你进行更新的时候对版本进行加1,对于A->B->A 就会变成1A ->2B->3A

七、使用CAS会引起的问题

1.ABA问题

ABA问题可以使用版本号解决

2.循环时间长开销大

 自旋CAS如果长时间不成功,CPU带来非常大的执行开销,需要考虑长时间循环问题,给每个线程循环给定循环次数阈值,让当前线程释放CPU的使用权,进入阻塞中

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

八、Synchronized锁优化

JDK1.5之前, Synchronized称之为“重量级锁”,对该做了各种所有,分别为偏向锁、轻量级锁、重量级锁

Java对象内存布局:

说到 synchronized 加锁原理与Java对象在内存中的布局有很大关系, Java 对象内存布局如下:

图片.png

如上图所示,在创建一个对象后,在 JVM 虚拟机( HotSpot )中,对象在 Java 内存中的存储布局 可分为三块:

对象头区域

存放锁信息,对象年龄等信息

实例数据区域

此处存储的是对象真正有效的信息,比如对象中所有字段的内容

对齐填充区域

JVM 的实现 HostSpot 规定对象的起始地址必须是 8 字节的整数倍,换句话来说,现在 64 位的 OS 往外读取数据的时候一次性读取 64bit 整数倍的数据,也就是 8 个字节,所以 HotSpot 为了高效读取对象,就做了"对齐",如果一个对象实际占的内存大小不是 8byte 的整数倍时,就"补位"到 8byte 的整数倍。所以对齐填充区域的大小不是固定的。

synchronized用的锁是存在Java对象头里的,如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽等于4字节,即32bit,如下图:

图片.png

Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。32位JVM的Mark Word的默认存储结构如下图所示:

图片.png

在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

图片.png

九、偏向锁

偏向锁的操作根本没有去找操作系统, 每个对象都有对象头,看看这个account对象的所谓“对象头”,其中有个叫做Mark Word:里边有几个标识位,还有其他数据。

图片.png

JVM使用CAS操作把线程ID记录到了这个Mark Word当中,修改了标识位,当前线程就拥有这把锁了

图片.png

可以看出:JVM不用和操作系统协商设置Mutex,它只记录下线程ID,就表示当前线程拥有这把锁了,不用操作系统介入

这时线程获得了锁,可以执行synchronized修饰的代码块。

当线程再次执行到这个synchronized的时候,JVM通过锁对象account的Mark Word判断:“当前线程ID还在,还持有着这个对象的锁,就可以继续进入临界区执行

这就是偏向锁,在没有别的线程竞争的时候,一直偏向当前线程,当前线程可以一直执行

十、轻量级锁

继续沿着偏向锁思路研究

另一个线程0x3704也要进入这个代码块执行,但是锁对象account 保存的是当前线程ID,他是没法进入临界区的。

这时也不需要和操作系统交流,JVM可以对偏向锁升级一下,变成一个轻量级的锁。

JVM把锁对象account恢复成无锁状态,在当前两线程的栈帧中各自分配了一个空间,叫做Lock Record,把锁对象account的Mark Word在俩线程的栈帧中各自复制了一份,叫做Displaced Mark Word

然后当前线程的Lock Record的地址使用CAS放到了Mark Word当中,并且把锁标志位改为00, 这意味着当前线程也已经获得了这个轻量级的锁了,可以继续进入临界区执行。

 图片.png

0x3704线程没有获得锁,但不阻塞,JVM让他自旋几次,等待一会儿。等当前退出临界区,释放锁的时候,需要把这个Displaced markd word 使用CAS复制回去。接下来他就可以加锁了。

两线程交替着进入临界区,执行这段代码,相安无事,很少出现真正的竞争。

即使是出现了竞争,想获得锁的线程只要自旋几次,等待一会儿,锁就可能释放了。

很明显,如果没有竞争或者轻度的竞争,轻量级锁仅仅使用CAS操作和Lock record就避免了重量级互斥锁的开销

十一、重量级锁

再次分析:轻量级锁运行时,一线程0x3704 正在持有锁。另一线程自旋了好多次,0x3704还是没释放锁。 这时候JVM考虑自旋次数太多了浪费CPU。接则升级为重量级锁!

重量级锁需要操作系统的介入,依赖操作系统底层的Mutex Lock。

JVM创建了一个monitor 对象,把这个对象的地址更新到了Mark word当中。

图片.png

在持有锁运行,而另一线程则切换进程状态至:阻塞

到此这篇关于详解Java中的悲观锁与乐观锁的文章就介绍到这了,更多相关悲观锁与乐观锁内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 详解Java中的悲观锁与乐观锁

本文链接: https://www.lsjlt.com/news/127190.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 详解Java中的悲观锁与乐观锁
    目录一、悲观锁二、乐观锁三、CAS四、AtomicXXX五、CAS中的ABA问题六、ABA问题解决方案七、使用CAS会引起的问题八、Synchronized锁优化九、偏向锁十、轻量级...
    99+
    2022-11-12
  • MySQL中的悲观锁与乐观锁
    在关系型数据库中,悲观锁与乐观锁是解决资源并发场景的解决方案,接下来将详细讲解🔎一下这两个并发解决方案的实际使用及优缺点。 首先定义一下数据库,做一个最简单的库存表,如下设计: CREATE TA...
    99+
    2022-05-15
    MySQL 悲观锁 MySQL 乐观锁
  • 怎么理解Java悲观锁与乐观锁
    本篇内容介绍了“怎么理解Java悲观锁与乐观锁”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1锁(Lock)在介绍悲观锁和乐观锁之前,让我们...
    99+
    2023-06-04
  • java中乐观锁与悲观锁的概念
    这篇文章主要介绍“java中乐观锁与悲观锁的概念”,在日常操作中,相信很多人在java中乐观锁与悲观锁的概念问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”java中乐观锁与悲观锁的概念”的疑惑有所帮助!接下来...
    99+
    2023-06-19
  • Java多线程之悲观锁与乐观锁
    目录1. 悲观锁存在的问题2. 通过CAS实现乐观锁3. 不可重入的自旋锁4. 可重入的自旋锁总结问题: 1、乐观锁和悲观锁的理解及如何实现,有哪些实现方式? 2、什么是乐观锁和悲观...
    99+
    2022-11-13
  • Java中的悲观锁与乐观锁怎么掌握
    要掌握Java中的悲观锁和乐观锁,可以遵循以下步骤:1. 了解悲观锁和乐观锁的概念:- 悲观锁(Pessimistic Lockin...
    99+
    2023-08-12
    Java
  • redis乐观锁与悲观锁的实战
    目录概念乐观锁悲观锁乐观锁示例悲观锁示例总结提升概念 Redis是一个内存中的键值存储系统,支持多种数据结构,如字符串、哈希、列表等。Redis提供了两种锁机制,即乐观锁和悲观锁。 乐观锁 乐观锁是一种乐观的并发控制策略...
    99+
    2023-04-13
    redis 乐观锁 悲观锁 redis 乐观锁 redis 悲观锁
  • 悲观锁,乐观锁的概念
    悲观锁:顾名思义,就是很悲观,它指的是对数据被外界(包括本系统当前的其他事务,以及来自其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁实现,往往依...
    99+
    2022-10-18
  • Yii框架实现乐观锁与悲观锁流程详解
    目录一、在Yii中实现乐观锁1、在yii中实现乐观锁步骤2、Yii中实现乐观锁3、实现乐观锁二、在Yii中实现悲观锁1、在yii中实现悲观锁的步骤2、yii中悲观锁实现一、在Yii中...
    99+
    2022-11-13
    Yii乐观锁与悲观锁 Yii悲观锁 Yii乐观锁
  • Mysql中悲观锁与乐观锁应用介绍
    目录1.锁2.悲观锁3.乐观锁4.如何选择1.锁 ​ 生活中:锁在我们身边无处不在,比如我出门玩去了需要把门锁上,比如我需要把钱放到保险柜里面,必须上锁以保证我财产的安全。 代码中:比如多个线程需要同时操作修改共享变量,...
    99+
    2022-08-18
    Mysql悲观锁与乐观锁 Mysql悲观锁 Mysql乐观锁
  • MySQL悲观锁与乐观锁如何实现
    这篇文章主要为大家展示了“MySQL悲观锁与乐观锁如何实现”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“MySQL悲观锁与乐观锁如何实现”这篇文章吧。前言悲观锁和乐观锁是用来解决并发问题的两种思...
    99+
    2023-06-25
  • redis乐观锁与悲观锁怎么使用
    本篇内容主要讲解“redis乐观锁与悲观锁怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“redis乐观锁与悲观锁怎么使用”吧!概念Redis是一个内存中的键值存储系统,支持多种数据结构,...
    99+
    2023-07-06
  • 合理的使用MySQL乐观锁与悲观锁
    针对 MySQL的乐观锁与悲观锁的使用,基本都是按照业务场景针对性使用的。针对每个业务场景,对应的使用锁。 但是两种锁无非都是解决并发所产生的问题。下面我们来看看如何合理的使用乐观锁与悲观锁   何为悲观锁 悲观锁(Pessi...
    99+
    2015-02-12
    合理的使用MySQL乐观锁与悲观锁
  • MySQL悲观锁与乐观锁的实现方案
    目录前言实战 1、无锁2、悲观锁3、乐观锁总结前言 悲观锁和乐观锁是用来解决并发问题的两种思想,在不同的平台有着各自的实现。例如在Java中,synchronized就可以认为是悲观...
    99+
    2022-11-12
  • Java中的乐观锁和悲观锁简单介绍
    这篇文章主要介绍“Java中的乐观锁和悲观锁简单介绍”,在日常操作中,相信很多人在Java中的乐观锁和悲观锁简单介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中的乐观锁和悲观锁简单介绍”的疑惑有所...
    99+
    2023-06-02
  • 浅谈一下Java中的悲观锁和乐观锁
    目录悲观锁(Pessimistic Locking)悲观锁存的问题:乐观锁乐观锁存在的问题悲观锁和乐观锁的对比总结悲观锁和乐观锁是面试高频问题之一,本文将对悲观锁和乐观锁简单的进行一...
    99+
    2023-05-16
    Java悲观锁 Java乐观锁
  • 一文秒懂Java中的乐观锁 VS 悲观锁
    乐观锁 VS 悲观锁 悲观锁:总是假设最坏的情况,每次取数据时都认为其他线程会修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起。 乐观锁:总是认为不...
    99+
    2022-11-12
  • 悲观锁与乐观锁怎么在Mysql中使用
    悲观锁与乐观锁怎么在Mysql中使用?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。悲观锁悲观锁,认为数据是悲观的。当我们查询数据的时候加上...
    99+
    2022-10-18
  • MySQL中乐观锁和悲观锁的区别
    这篇文章将为大家详细讲解有关MySQL中乐观锁和悲观锁的区别,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。悲观锁在关系数据库管理系统中,悲观并发控制(悲观锁...
    99+
    2022-10-18
  • java中悲观锁和乐观锁有什么区别
    本篇文章给大家分享的是有关java中悲观锁和乐观锁有什么区别,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Java可以用来干什么Java主要应用于:1. web开发;2. An...
    99+
    2023-06-14
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作