【Java多线程进阶】常见的锁策略

news2024/11/18 1:27:11

前言

众所周知,拳击运动员是要分等级(轻量级、重量级等等)来参加比赛的,在 Java 多线程中 锁(synchronized) 也会根据锁的竞争程度来升级为相关“高等级”锁,为了更好的理解 synchronized 加锁机制,我把锁的相关策略整理出来了,一起来看看吧。

目录

1. 悲观锁与乐观锁

2. 读写锁与互斥锁

3. 重量级锁与轻量级锁

4. 自旋锁与挂起等待锁

4.1 自旋锁

4.2 挂起等待锁

5. 公平锁与非公平锁

6. 可重入锁与不可重入锁

1. 悲观锁与乐观锁

悲观锁:为了保证原子性,因此把数据进行上锁,每一个不同的线程拿数据的时候都会参与锁的竞争,其他线程想必须等待前者拿完数据解锁后才能参与拿数据。

举例,由于维修导致一层楼只剩下一间厕所。因此,线程1进入厕所后,其他线程只能阻塞等待。


乐观锁:假设数据一般情况下不会产生并发冲突,所以在数据进行提交更新的时候,才会正式对数据是否产生并发冲突进行检测,如果发现并发冲突了,则让返回用户错误的信息,让用户决定如何去做。

还是上述上厕所例子,线程1 给其他线返回一个信息,其他线程可根据信息选择换楼层上厕所亦或是等待。

以上的上厕所例子,在楼栋厕所不充足情况下。线程还是盲目选择这栋楼的厕所(使用悲观锁)就会导致阻塞消耗系统资源。在楼栋厕所充足的情况下,线程选择了这栋楼的厕所(使用乐观锁)这样就能很好的利用系统资源。

synchronized 初始情况下使用的是乐观锁,当发现锁竞争激烈时候就会自动转换为悲观锁。就好比一个线程去某一栋楼上厕所,并不知道该楼栋是否厕所充足。充足就不阻塞等待,不充足就阻塞等待。


2. 读写锁与互斥锁

多线程中,线程作为读取方不会产生线程安全问题,当线程作为为写入方和线程作为写入方之间进行交互和,线程作为写入方和线程作为读取方之间进行交互,就会造成互斥。

线程对数据的访问,主要存在三种情况:

  • 线程只是对数据进行读操作,此时自然不会出现线程不安全问题。
  • 多个线程对数据进行写操作,就会出现线程不安全问题。
  • 一个线程对数据进行读操作,另个线程对数据进行写操作,也会出现线程不安全问题。

简单的来说,线程的读操作,就是线程对数据进行访问。线程的写操作,就是线程对数据进行修改。读一下问题不大,但写一下就难免会造成意外。因此,我们有了 读写锁 这个概念。


读写锁,就是把读和写这两个操作分开来加锁这样就能避免互斥。Java 标准库提供了一个 ReentrantReadWriteLock 类,实现了读写锁。

ReentrantReadWriteLock.ReadLock 类表示一个读锁.。这个对象提供了 lock / unlock 方法进行加锁解锁。

ReentrantReadWriteLock.WriteLock 类表示一个写锁.。这个对象也提供了 lock / unlock 方法进行加锁解锁。

  • 读加锁与读加锁之间,不互斥
  • 写加锁与写加锁之间,互斥
  • 读加锁与写加锁之间,互斥

互斥,就会操作线程的挂起等待,一旦线程挂起等待了,就不知道什么时候能够被唤醒了。因此,我们在编写代码的时候尽可能减少互斥。

读写锁特别适用于“频繁读,不频繁写”的场景中。比如,学校的教务系统:

假设计算机软件专业的学生有 300 个同学,这300个同学几乎每天都要课程表为了防止课表更改,这样的一个操作就是频繁读(访问)。

有特殊情况,老师生病了或是怎样,偶尔会调课到其他时间点。这样的操作,就是不频繁写(修改)。

注意synchronized 不是读写锁


3. 重量级锁与轻量级锁

在并发编程中,轻量级锁和重量级锁是两种锁的实现方法,主要用于解决多个线程同时访问共享资源时的同步问题。

轻量级锁通常用于锁竞争不激烈的情况下,通过在线程内部使用CAS操作来进行加锁和解锁,这种方式不需要进行线程的上下文切换,因此性能比重量级锁更高。但是,如果锁竞争激烈的话,轻量级锁的性能优势就不明显了。

重量级锁通常用于锁竞争激烈的情况下,通过将竞争锁的线程挂起并切换到内核态来进行加锁和解锁。由于需要进行线程的上下文切换,因此性能比轻量级锁更低。但是,在锁竞争激烈的情况下,重量级锁的效果要比轻量级锁好得多,因为它可以有效地避免锁争用问题,减少了线程的抢占和切换,从而提高了系统的效率和响应速度。

synchronized 的轻量级锁策略大概都是通过自旋锁的方式实现的,重量级锁则是挂起等待锁。


4. 自旋锁与挂起等待锁

自旋锁 VS 挂起等待锁:

自旋锁,当线程之间进行抢占锁内资源时候,线程1 已经抢占到锁,线程2 则会持续等待 线程1 锁内任务结束后再进行抢占锁资源,在这期间 线程2 持续处于阻塞等待状态。

挂起等待锁,当挂起等待锁遇到这种情况时,发现有线程已经抢占到锁了,则会放弃阻塞等待。直到锁开放了,则再参与抢占锁。

因此,自旋锁有以下优缺点:

  • 优点:时刻占用系统资源,不涉及线程阻塞和调度,一旦锁被释放了,参与锁的竞争。
  • 缺点:当锁内任务比较复杂时,锁被其他线程占有时间过长,那么就会持续消耗系统资源。
  • 挂起等待锁则相反

4.1 自旋锁

自旋锁,按照正常的逻辑,当线程抢占锁时进入阻塞状态,过不了多久锁就被释放了。因此,自旋锁就没必要放弃 CPU 了,一直占用着 CPU 的内存空间。

自旋锁伪代码:

while(枪锁lock == 失败) {}

如果获取锁失败,立即再尝试获取锁,无限循环下去直到获取到锁为止。第一次获取锁失败,往后的获取锁操作会在极短的时间内到来,一旦锁被其他线程释放,就能第一时间获取到锁。这就是轻量级锁的体现(锁的竞争还不太激烈,尝试使用自旋方式加锁)。


4.2 挂起等待锁

当线程获取锁失败后,并不会进行阻塞等待。而随着系统的调度,不占用 CPU 。直到锁开发后,再尝试参与锁竞争。这种情况就是挂起等待锁,也是重量级锁的体现(锁的竞争太激烈了,线程跟随系统的调度)。

举例:

自旋锁与挂起等待锁的现实生活体现:张三是一个普通的男生,如花是一个漂亮的女孩,在此张三作为线程,如花作为锁。

张三开始追求如花,但是如花已经有男朋友了。张三又是个死皮赖脸的人。每天坚持给女孩发信息,期待着某一天如花分手,能得到如花。此时张三就处于自旋锁的状态。

随着竞争的激烈,又有许多人想要追求如花。张三开始动摇了,开始努力敲代码、认真学习不参与追如花的竞争了(随着系统的调度做其他事去了)。如果某一天如花变为单身了,系统会通知张三如花单身了(锁空闲了),张三就又开始参与竞争锁。此时张三的状态就是挂起等待锁状态。


5. 公平锁与非公平锁

公平锁与非公平锁讲究四个字“公平竞争”,假设有三个线程抢占锁资源,当锁被释放后就会出现两种情况:公平竞争锁、非公平竞争锁。

公平锁:遵循先来后到的原则,线程1 进入锁,锁释放后。线程2 进入锁,锁再释放后。线程3 进入锁。整个过程是按照顺序执行的。

非公平锁:由于线程之间抢占资源,导致锁被无序的抢占。这样 3 个线程都有机会优先进入锁。整个过程会造成无序执行。

通过上图我们就能很好的理解,公平锁与非公平锁之间的差异。当然,线程的调度是随机的因此多个线程竞争锁时可以随意进行抢占“手快有,手慢无”(非公平锁)。要想实现公平锁,我们可以使用一些特定的数据结构来达到按顺序使用锁。

在实际开发中,公平锁与非公平锁没有好坏之分,我们按照需求来进行设置。注意,synchronized 属于非公平锁。


6. 可重入锁与不可重入锁

可重入锁即允许一个线程多次获取同一把锁。

不可重入锁是指一旦线程获得了该锁,此时再次请求获取该锁时,系统会将该线程挂起,直到该锁被释放为止。因此,不可重入锁不能再同一线程中重复获取。

可重入锁是指当一个线程获得了该锁之后,在该锁还未释放之前,可以再次获取该锁。这种锁可以防止死锁的发生,因为在获取之后可以在方法中重新获取该锁,从而避免死锁的发生。

Java 中的 synchronized 关键字是一种可重入锁,而 ReentrantLock 是 Java 中常用的可重入锁类,synchronized 不需要手动解锁,而 ReentrantLock 需要手动解锁。

需要注意的是,可重入锁虽然提高了代码的灵活性和可维护性,但同时也可能会带来出现深度嵌套锁的风险,引发死锁或性能下降等问题。因此,在使用可重入锁时需要仔细设计和管理。


谈谈你对synchronized的演变过程的理解?

synchronized 既是悲观锁也是乐观锁,synchronized 即是轻量级锁也是重量级锁,synchronized 即是自旋锁也是挂起等待锁,synchronized 不是读写锁,synchronized 是非公平锁,synchronized是可重入锁。

synchronized 的初始化的时候是一个乐观锁/轻量级锁/自旋锁,随着synchronized的竞争激烈会升级为悲观锁/重量级锁/挂起等待锁,另外轻量级锁是部分基于自旋锁、重量级锁是部分基于挂起等待锁。


在锁的策略中还会引申到“死锁”的概念,在下篇博文中,我会介绍。大家也可以通过下方专栏中搜索多线程相关内容。

🧑‍💻作者:一只爱打拳的程序猿,Java领域新星创作者,阿里云社区优质创作者、专家博主。

📒博客主页:这是博主的主页 

🗃️文章收录于:Java多线程编程 

🗂️JavaSE的学习:JavaSE 

🗂️Java数据结构:数据结构与算法 

 本篇博文到这里就结束了,感谢点赞,评论,收藏,关注~

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

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

相关文章

微信小程序node+vue医院挂号预约系统fun17

从而实现管理员后端;首页、个人中心、用户管理、专家管理、科室类型管理、职称类型管理、医院挂号管理、挂号信息管理、留言板管理、系统管理,专家后端;首页、个人中心、医院挂号管理、挂号信息管理、系统管理,用户前端&#xff1…

【Linux】网络基础+UDP网络套接字编程

只做自己喜欢做的事情,不被社会和时代裹挟着前进,是一件很奢侈的事。 文章目录 一、 网络基础1.局域网和广域网2.协议初识和网络协议分层(TCP/IP四层模型)3.MAC地址和IP地址(子网掩码,路由表,I…

美国金融科技公司SoFi的增长难以持久,股价也将下跌

来源:猛兽财经 作者:猛兽财经 公司介绍 SoFi Technologies(SoFi)是一家来自美国的知名金融科技公司,自2011年成立以来,已成为领先的个人理财在线平台。SoFi为年轻的高收入客户提供多样化的产品和服务,包括学生和汽车贷…

如何在 Python 中使用断点调试

入门教程、案例源码、学习资料、读者群 请访问: python666.cn 实际上没人能一次就写出完美的代码,除了我。但是世界上只有一个我。 林纳斯托瓦兹(Linux 之父) 大家好,欢迎来到 Crossin的编程教室 ! 上面这段…

【CSS3系列】第二章 · CSS3 新增盒模型和背景属性

写在前面 Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正&#xff0…

大数据:数据表操作,分区表,分桶表,修改表,array,map, struct

大数据:数据表操作,分区表 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学生都得去找开发,测开 测开的话,你就得学数据库,sql,oracle&a…

【能量算子】评估 EEG 中的瞬时能量:非负、频率加权能量算子(PythonMatlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

五种方法提升Midjourney的出图品质

本文基于B站UP主琥珀川Eric的《五种方法提升Midjourney出图品质》制作在此感谢大神的分享。 本文全面介绍以上五种提升Midjourney出图品质的方法,简单实用,马上就可以用上。Lets go!!! 方法一 使用相机参数创建逼真的图…

windows系统编译的Qt程序转到国产化麒麟linux中编译

团队自研股票软件,关威信共总号:QStockView,下载 1.1 windows系统编译的Qt程序转到国产化麒麟linux中编译 (1)把Vs工程项目文件导入到Linux中 首先把vs的工程拷贝到linux里面(可以用虚拟机的共享文件夹…

适配器模式的学习与使用

1、适配器模式的学习 当我们需要将一个类的接口转换成另一个客户端所期望的接口时,适配器模式(Adapter Pattern)可以派上用场。它允许不兼容的接口之间能够协同工作。   适配器模式属于结构型设计模式,它包含以下几个角色&#…

2、数据库:SQL Server部署 - 系统部署系列文章

对于微软的SQL Server的安装,以前已经有写过了,到了2022版本,安装没多大的改变,很多只需要少配置,然后直接下一步即可。现在是2023年了,SQL Server已经出到了2022版本,这篇博文就再次对SQL Serv…

chatgpt赋能python:Python列表按长度排序的方法

Python列表按长度排序的方法 在Python编程中,列表是最常用的数据结构之一。列表是一种可变的有序序列,可以包含任意类型的对象。有时候,我们需要对列表按照元素的长度进行排序。本文将介绍Python中列表按长度排序的两种方法。 方法一&#…

pytorch实战 -- 神经网络

softmax的基本概念 交叉熵损失函数 模型训练和预测 在训练好softmax回归模型后,给定任一样本特征,就可以预测每个输出类别的概率。通常,我们把预测概率最大的类别作为输出类别。如果它与真实类别(标签)一致&#xff0…

chatgpt赋能python:Python列表排序详解:从基础排序到高级算法

Python 列表排序详解:从基础排序到高级算法 在 Python 编程中,列表是常用的数据类型。列表的排序是其中重要的操作之一。Python 提供了多种方法来对列表进行排序,从简单的基础排序到高级的算法排序。在这篇文章中,我们将详细介绍…

找到 FSM 的区别序列、UIO 或特征集(W方法)

找到 FSM 的区别序列、UIO 或特征集(W方法) 1 简介 许多系统都是基于状态的:它们有一个更新的内部状态通过操作并影响行为。 在测试这样一个系统时,一个需要考虑状态。 这导致了一系列的语言,用于描述基于状态的规范和模型,这些可…

并发编程-系统学习篇

并发编程的掌握过程并不容易。 我相信为了解决这个问题,你也听别人总结过:并发编程的第 一原则, 那就是不要写并发程序 这个原则在我刚毕业的那几年曾经是行得通的,那个时候多核服务器还是一种奢侈品,系统的并发量也很…

沙盒不再高端,Windows11将自带沙盒让程序检测更方便

Windows 沙盒提供了轻型桌面环境,可以安全地在隔离状态下运行应用程序。 安装在 Windows 沙盒环境下的软件保持“沙盒”状态,并且与主机分开运行。 沙盒是临时的。 当关闭沙盒后,系统将删除所有软件和文件以及状态。 每次使用时,…

AWK常用用法

awk简介 awk其名称得自于它的创始人 Alfred Aho 、Peter Weinberger 和 Brian Kernighan 姓氏的首个字母。实际上 AWK 的确拥有自己的语言: AWK 程序设计语言 , 三位创建者已将它正式定义为“样式扫描和处理语言”。它允许您创建简短的程序,…

解决一个典型的商业案例研究任务

介绍 印尼的一家公司 Gojek 通过移动应用程序提供运输和物流、食品和购物、支付、日常需求、商业、新闻和娱乐等服务,对经济做出了超过70亿美元的贡献。 它拥有 90 万注册商户、超过 1.9 亿次应用下载以及超过 200 万名司机能够在120分钟内完成超过18万个订单。我们…

chatgpt赋能python:Python创建界面的重要性及实现方法

Python创建界面的重要性及实现方法 作为一名有10年Python编程经验的工程师,我深知Python在Web开发、数据分析和人工智能等方面的强大表现。然而,Python对于前端的支持一直是一个不被关注的领域。 随着网站、移动应用和电脑软件的普及,用户对…