Java中各种锁的详细介绍(二):悲观锁和乐观锁

2023-05-16

Java中锁的类型多种多样,有简单有复杂,适合各种不同的应用场景,接下来会分几章给大家详细介绍java中各种类型的锁。

一、悲观锁和乐观锁的说明

1、悲观锁(Pessimistic Lock):对于同一个数据的并发操作,想的很坏,很悲观,都认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。别的线程想拿数据就被挡住,直到悲观锁被释放,悲观锁中的共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。

此外阻塞、唤醒以及引起的CPU状态切换等处理悲观锁的机制会产生额外的开销,还有增加产生死锁的机会,另外还会降低程序的并行性。

Java中synchronized关键字和Lock的实现类,以及数据库中的行锁、表锁、读锁(共享锁)和写锁(排他锁)都是悲观锁。

2、乐观锁(Optimistic Lock):很乐观,每次去拿数据的时候都认为别的线程不会修改。所以不会上锁,只有在想要更新数据时候,去检查在读取至更新这段时间别的线程有没有修改过这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入;如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。

乐观锁在Java中是通过使用无锁编程来实现,所以不会产生任何锁和死锁,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

数据库实现乐观锁并不会使用数据库提供的锁机制。一般实现乐观锁的方式就是数据表字段增加版本号(version)或者是时间戳来实现,使用版本号是最常用的。

二、悲观锁和乐观锁的调用方式

1、悲观锁的调用方式

//悲观锁的调用方式
// synchronized
public synchronized void testMethod() {
	// 操作同步资源
}
// Reentrantlock
private ReentrantLock lock = new ReentrantLock(); // 需要保证多个线程使用同一个锁
public void modifyPublicResources() {
	lock.lock();
	//操作同步资源
	lock.unlock();
}

悲观锁通过显式的锁定再操作同步资源,但是如果存在嵌套锁的情况下,会出现死锁,例如:

public class DeadlockExample {
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {
            System.out.println("method1 acquired lock1");
            synchronized (lock2) {
                System.out.println("method1 acquired lock2");
            }
        }
    }
    
    public void method2() {
        synchronized (lock2) {
            System.out.println("method2 acquired lock2");
            synchronized (lock1) {
                System.out.println("method2 acquired lock1");
            }
        }
    }
}

在上面的代码中,method1()获取lock1锁后,又尝试获取lock2锁;method2()获取lock2锁后,又尝试获取lock1锁。如果两个线程分别调用这两个方法,且在相应的时刻互相等待对方释放锁,就会出现死锁。实际应用中解决这种问题的方法是尽量避免嵌套锁的使用,并且对锁的获取顺序进行规定。比如,在上面的代码中,可以规定获取锁的顺序为先获取lock1锁,再获取lock2锁,这样就可以避免死锁的出现。

2、乐观锁的调用方式

public class OptimisticLockDemo {
    private String value;
    private AtomicInteger version = new AtomicInteger(0);

    public void update(String newValue) {
        while (true) {
            int currentVersion = version.get();
            //业务处理
            if (currentVersion == version.get()) {
                value = newValue;
                version.incrementAndGet();
                break;
            }
        }
    }
}

 乐观锁采用无锁方式,所以不存在死锁的情况,因此在并发性能上会更加优越,适用于读多写少的场景

三、乐观锁的实现方式CAS

1、CAS简介

通过调用方式示例,我们可以发现悲观锁基本都是在显式的锁定之后再操作同步资源,而乐观锁则直接去操作同步资源。那么,为何乐观锁能够做到不锁定同步资源也可以正确的实现线程同步呢?我们通过介绍乐观锁的主要实现方式 “CAS” 的技术原理来为大家解惑。

CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。

CAS算法涉及到三个操作数:

  • 需要读写的内存值 V。

  • 进行比较的值 A。

  • 要写入的新值 B。

当且仅当 V 的值等于 A 时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。一般情况下,“更新”是一个不断重试的操作。

2、CAS存在的三大问题

CAS虽然很高效,但是它也存在三大问题,这里也简单说一下:

1)、 ABA问题。CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。

JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题,具体操作封装在compareAndSet()中。

2)、循环时间长开销大。CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。

3)、只能保证一个共享变量的原子操作。对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。

Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。

四、悲观锁和乐观锁的应用场景

悲观锁阻塞线程,乐观锁回滚重试,他们各有优缺点,不分仲伯,适合不同的场景:

1、悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。

2、乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升

关于数据库实现悲观锁和乐观锁的示例可以参考文章:

悲观锁与乐观锁的实现(详情图解)_乐观锁和悲欢锁的实现_零点零六了的博客-CSDN博客

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

Java中各种锁的详细介绍(二):悲观锁和乐观锁 的相关文章

  • 不同的 JDK 更新会产生不同的 Java 字节码吗?

    假设场景 我有一个项目 其源合规性级别指定为 1 5 现在 我使用两种不同的 JDK 编译此项目 首先使用 JDK 6 Update 7 然后使用 JDK 6 Update 20 这两个不同的 JDK 是否会生成不同的 Java 字节代码
  • 使用 Spring MVC 返回 PDF 文件

    实际上 我有这个功能 我有一个框架 可以在其中设置 URL ip port birt preview report report rptdesign format pdf parameters 并且该框架呈现 PDF 文件 但我想隐藏该网址
  • Spring Data:限制自定义查询的结果

    在我的 Spring 数据存储库中 我 必须 使用自定义查询 Query注解 我知道我可以限制这样的命名查询中的结果数量 Iterable
  • 使用 java 的 RAR 档案 [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • MongoDB:尝试从 JSON 读取 Long 导致 java.lang.Integer 无法转换为 java.lang.Long

    我有一个代码可以从 MongoDB 读取特定格式的数据 我需要测试一下 为此 我使用要测试的数据创建一个 JSON id ObjectId 57552e32e4b0839ede67e0af serial 574000690 startDat
  • Ant 无法启动,给出主类错误

    我正在运行 Elementary OS 基于 Ubuntu 12 并且在运行 apache ant 时遇到问题 它在重新启动之前就可以正常工作 所以我不确定会发生什么变化 我在 etc environment 中定义了环境变量 如下所示 P
  • Java 反射:如何检索匿名内部类?

    我在另一个类中有一个匿名内部类 SomeClass Both SomeClass class getClasses and SomeClass class getDeclaredClasses 返回空数组 我在中找不到一些关于此的提示Cla
  • 将多个视频文件合并到一个文件中

    我有多个以相同帧速率和分辨率录制的视频 我想将两个视频合并为一个视频 因此结果文件将是大视频 我正在使用 MP4 解析器 api 并使用下面的代码 Movie countVideo new MovieCreator build Channe
  • 在Java中使用BufferedWriter写入文件时监视文件大小?

    我正在将一个可能很长的项目列表写入文件 我正在写的项目的长度是可变的 如果生成的文件大小大于10M 则应将其分成多个文件 为了提高性能 我目前使用 BufferedWriter 如下所示 final FileOutputStream fos
  • 给定一个单词列表 - 在 java 中完成单词的好的算法是什么?权衡:速度/效率/内存占用

    我正在探索潜在的免费 付费应用程序的硬件 软件要求 最终目标是移动 Java 应用程序 该应用程序将从这个简单的目标开始 给定数据库中相关单词的列表 能够对单个字符串输入进行单词补全 换句话说 我已经知道数据库的内容 但算法的内存占用 速度
  • 使用 CrudRepository 进行自定义查询

    我想使用 CrudRepository 自定义查询 这是我的代码 Repository public interface CustomerRepository extends CrudRepository
  • Java 套接字:可以从一个线程发送并在另一个线程上接收吗?

    这可能是一个非常基本的问题 但我很难找到答案 让一个线程写入 Socket 的输出流 而另一个线程从 Socket 的输入流读取数据 这样可以吗 编辑 这是一个与外部服务器通信的客户端应用程序 我并不是想让两个线程互相交谈 很抱歉含糊不清
  • 驱动程序信息:driver.version:未知,使用 ChromeDriver v78.0.3904.70 和 Chrome 浏览器 v78.0.3904.97

    我使用的是java 1 8和chrome浏览器版本78 0 3904 97 我正在尝试使用 chrome 驱动程序版本执行我的 selenium 脚本代码78 0 3904 70 但在执行时我面临以下问题并且 chrome 立即崩溃 Pic
  • activemq 的优先级

    我们目前正在使用 JMS 和 activemq 5 5 1 开发一个应用程序 我们想为某些消息定义更高的优先级 这将使它们首先被消耗 设置生产者和消费者后 通过spring 3 1 JMSTemplate 优先级并不能完全发挥作用 事实上
  • 使用 Java 通过 HTTP 下载未知长度的文件

    我想用java下载一个HTTP查询 但是我下载的文件在下载时有一个未确定的长度 我认为这将是相当标准的 所以我搜索并找到了它的代码片段 http snipplr com view 33805 http snipplr com view 33
  • 将变量从 jenkins 传递到 testng.xml

    我想根据从詹金斯传递的变量运行测试用例 例如 选择您要运行的测试用例 测试用例一 测试用例二 在 pom xml maven 中
  • Apache HttpClient TCP Keep-Alive(套接字保持活动)

    我的 http 请求需要太多时间才能被服务器处理 大约 5 分钟 由于连接闲置 5 分钟 代理服务器将关闭连接 我正在尝试在 Apache DefaultHttpClient 中使用 TCP Keep Alive 来使连接长时间处于活动状态
  • Python 可以替代 Java 小程序吗?

    除了制作用于物理模拟 如抛射运动 重力等 的教育性 Java 小程序之外 还有其他选择吗 如果你想让它在浏览器中运行 你可以使用PyJamas http pyjs org 这是一个 Python 到 Javascript 的编译器和工具集
  • Drools:为什么是无状态会话?

    Drools 使用会话来存储运行时数据 为此 有两种会话 无状态和有状态 与无状态会话相比 有状态会话允许迭代调用 并且似乎比无状态会话具有所有优势 那么为什么会有无状态会话呢 他们服务的目的是什么 与有状态会话相比 它们的优势是什么 谢谢
  • 使用 Hibernate 防止无限循环数据检索

    我想知道 想象一个场景 例如 POJO public class User private String userName private String name private String surname private List

随机推荐

  • vc版本与vs版本对应关系

    vc版本与vs版本对应关系 最近在整理之前代码 xff0c 用cmake编译一直报错 xff0c 忘记了opencv3 1 0不支持vs2019 xff0c 所以在这里总结下vc版本与vs版本对应关系 VC版本号 VS对应版本 vc6 VC
  • cmake编译依赖opencv的c++库

    前面一篇主要讲了c 43 43 项目怎么在本地配置opencv过程 xff0c 这种方式缺点就是只能在开发着本地环境编译 xff0c 换台电脑就会出现环境配置问题 接下来主要讲解 xff0c 使用cmake编译 xff0c 生成一个依赖op
  • c++ stl 迭代器iterators(traits编程技法)

    文章目录 1 1 迭代器设计思维 stl关键所在1 2 迭代器是一种smart pointer1 3 迭代器相应型别 xff08 associated types xff09 1 4 traits编程技法 stl源代码门匙1 4 1 val
  • 如何用算法把一个十进制数转为十六进制数-C语言基础

    这一篇文章要探讨的是 如何用算法实现十进制转十六进制 并不涉及什么特别的知识点 属于C语言基础篇 在翻找素材的时候 xff0c 发现一篇以前写的挺有意思的代码 xff0c 这篇代码里面涉及的知识点没有什么好讲的 xff0c 也没有什么特别的
  • 关于 Qt使用QJsonObject解析失败的问题。

    1 问题 在QJsonObject转 toInt toLongLong 等类型时 xff0c 转换失败 但是转toString xff08 xff09 没有任何问题 列如 xff1a 解决方法 xff1a 这样 xff0c 就可以结局问题
  • char 和 string 的相互转换

    一个char字符转为string span class token keyword char span ch span class token operator 61 span span class token char 39 A 39 s
  • C++STL标准库学习总结/索引/学习建议

    前言 xff1a 如果刚刚开始学习STL标准库 xff0c 不知道从哪里入手学习的话 xff0c 建议去中国大学mooc平台 xff0c 先学习北京大学郭炜老师的 程序设计与算法 xff08 一 xff09 C语言程序设计 xff08 ht
  • Python 调用API接口方式,通过http.client调用api接口,远程调用flask接口方式

    一 创建接口 xff08 如果调用别人的接口 xff0c 跳过此条 xff09 如果没有api xff0c 首先自己写一个接口玩一下 xff1a 必备知识 xff1a 一个项目最基本的文件 xff0c 接口run py文件 config文件
  • git tag和branch的区别

    tag 和branch的区别 Git tag是一系列commit的中的一个点 xff0c 只能查看 xff0c 不能移动 branch是一系列串联的commit的线 git tag的用法 我们常常在代码封板时 使用git 创建一个tag 这
  • 结构体对齐计算(超详细讲解,一看就会)

    想要计算结构体大小 xff0c 咱就先要清楚结构体内存对齐的规则 xff1a 1 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0处偏移 2 从第二个成员开始 xff0c 要对齐到某个 对齐数 的整数倍的偏移处 3 结构体的总大小
  • RTK差分编码

    一 概念 DCB xff08 Differential Code Bias 差分码偏差 xff09 是全球卫星导航系统 xff08 GNSS xff09 中 xff0c 通过不同信号得到的观测值之间存在的系统性偏差 DCB是由卫星和接收机硬
  • 详解JAVA的事件监听机制和观察者设计模式

    一 事件监听机制的三要素 事件源 事件监听器 xff0c 事件对象 监听器一般是JAVA接口 xff0c 用来约定可以执行的操作 二 事件监听机制简要说明 事件源注册一个或者多个事件监听器 xff0c 事件源对象状态发生变化或者被操作时 x
  • Nginx控制IP(段)的访问策略配置

    Nginx engine x 是一个高性能的HTTP和反向代理web服务器 xff0c 同时也提供了IMAP POP3 SMTP服务 有着负载均衡 动静分离等强大的功能 xff0c 而且还有众多三方插件来满足应用要求 这里重点介绍nginx
  • 敏捷开发-互联网时代的软件开发方式

    一 什么是敏捷开发 敏捷开发简单的描述为 xff1a 是一种应对需求快速变化的软件开发方式 敏捷开发的核心思想就是小步快跑 不断迭代 xff0c 在一次次的迭代升级中完成 小目标 最终完成那个 大目标 正因为敏捷开发的这种不断迭代升级的开发
  • Window系统查看端口是否启用以及占用程序

    1 打开DOS命令行窗口 开始 gt 运行 gt cmd xff0c 或者是 window 43 R gt cmd xff0c 调出命令窗口 2 查看当前正在使用的所有端口 命令 xff1a netstat ao 包括协议 xff0c 端口
  • ThreadLocal的深度解读

    一 J2SE的原始描述 This class provides thread local variables These variables differ from their normal counterparts in that eac
  • 消息中间件如何保证消息不丢失

    一 消息队列MQ的三个阶段 1 生产者发送消息到MQ 2 MQ存储消息到内存或者硬盘 3 消费者消费消息 由于网络的原因 服务器的原因 程序的原因等等 xff0c 在每个阶段都有可能引起消息的丢失 xff1a 1 生产者发送消息到MQ xf
  • 32位系统为什么最大只支持4GB运存?

    首先要明白 1B 61 2 3b 1KB 61 2 10B 1MB 61 2 20B 1GB 61 2 30B 4GB 61 2 2 2 30B 61 2 32B b表示一个比特位 xff0c B表示一个字节 xff0c 一字节等于8个比特
  • 数据库和Spring事务隔离级别

    事务隔离级别 xff0c 指的是数据库多个并发事务操作共享数据时 xff0c 共享的数据对多个并发事务之间的可见性和影响程度 隔离的内容主要指数据方面 具体举例来说就是一个事务A读操作时 xff0c 其他并发事务修改操作事务A读的数据时 x
  • Java中各种锁的详细介绍(二):悲观锁和乐观锁

    Java中锁的类型多种多样 xff0c 有简单有复杂 xff0c 适合各种不同的应用场景 xff0c 接下来会分几章给大家详细介绍java中各种类型的锁 一 悲观锁和乐观锁的说明 1 悲观锁 Pessimistic Lock xff1a 对