线程安全(一)Java锁(锁分类、锁升级、锁优化)

目录

    • 一、乐观锁
    • 二、悲观锁
    • 三、自旋锁
      • 3.1 自旋锁的优缺点:
      • 3.2 自旋锁的时间阈值:
      • 3.3 自旋锁的开启:
    • 四、Synchronized 同步锁
      • 4.1 Synchronized 作用范围:
      • 4.2 Synchronized 核心组件:
      • 4.3 Synchronized 实现:
      • 4.4 Synchronize 补充:
    • 五、ReentractLock 锁
      • 5.1 Lock 接口的主要方法:
      • 5.2 ReentrantLock 与 synchronized 对比:
      • 5.3 Condition 接口
      • 5.4 ReentrantLock 使用示例1:基本使用
      • 5.5 ReentrantLock 使用示例2:生产者-消费者模式
      • 5.6 Condition 类与 Object 类锁方法的区别:
      • 5.7 lock()、tryLock()、lockInterruptibly() 的区别:
    • 六、Semaphore 信号量
      • 6.1 实现互斥锁(Semaphore=1)
      • 6.2 Semaphore 使用示例:
      • 6.3 Semaphore 与 ReentrantLock 对比:
    • 七、AtomicInteger 等原子操作
    • 八、可重入锁(递归锁)
    • 九、公平锁(Fair)与非公平锁(Nonfair)
    • 十、ReadWriteLock 读写锁
      • 10.1 读锁:
      • 10.2 写锁:
    • 十一、共享锁与独占锁
      • 11.1 独占锁:
      • 11.2 共享锁:
      • 11.3 常见的独占锁、共享锁:
    • 十二、锁升级
      • 12.1 锁升级
      • 12.2 重量级锁
      • 12.3 轻量级锁
      • 12.4 偏向锁
    • 十三、分段锁
    • 十四、锁优化的手段
      • 14.1 减少锁持有的时间
      • 14.2 减小锁粒度
      • 14.3 锁分离
      • 14.4 锁粗化
      • 14.5 锁消除

前言:

  • 多线程一直是很多中级Java开发的面试核心提问点,在高并发环境下,线程安全的处理一直是如履薄冰。本文针对各种常见的 Java 锁进行了整理归纳,包括:
  • 乐观锁、悲观锁、自旋锁、Synchronized同步锁、ReentrantLock、Semaphore信号量、AtomicInteger、可重入锁、公平锁与非公平锁、ReadWriteLock读写锁、共享锁与独占锁、锁升级、锁优化。

一、乐观锁

  • 乐观锁 是一种乐观思想,即认为 读多写少,遇到并发的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁。

乐观锁的实际操作过程:

  • 在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(跟上一次的版本号比较,如果一样则更新),如果失败则要重复 -> 比较 -> 的操作。

Java 中的乐观锁基本都是通过 CAS 操作实现的,CAS 是一种更新的原子操作:比较当前值跟传入值是否一样,一样则更新,否则失败


二、悲观锁

  • 悲观锁 就是一种悲观思想,即认为 写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以 每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。

Java中的悲观锁就是 Synchronized,AQS 框架下的锁则是先尝试 CAS 乐观锁去获取锁,获取不到,才会转换为悲观锁,如:ReentrantLock。


三、自旋锁

  • 自旋锁 原理非常简单,如果持有锁的线程能在很短时间内释放所资源,那么那些等待竞争所的线程就不需要做 内核态用户态 之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换消耗。

线程自旋是需要消耗 CPU 的,说白了就是让 CPU 在做无用功,如果一直获取不到锁,那线程也不能一直占用 CPU 自旋做无用功,所以需要设定一个 自旋等待的最大时间

如果持有锁的线程执行时间 超过 自旋等待的最大时间仍没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会 停止自旋进入阻塞状态

3.1 自旋锁的优缺点:

  • 优点: 自旋锁 尽可能的减少线程的阻塞,这 对于锁的竞争不激烈,且占用所时间非常短的代码块来说性能可以大幅度地提升,因为自旋的消耗会小于线程阻塞挂起再唤醒操作的消耗,这些操作会导致 线程发生两次上下文切换。
  • 缺点: 如果锁的竞争的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 CPU 做无用功,占着 XX 不 XX,同时 如果有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其他需要 CPU 的线程又不能获取到 CPU,造成 CPU 的浪费。所以这种情况我们要关闭自旋锁。

3.2 自旋锁的时间阈值:

自旋锁的目的是为了占着 CPU 的资源不释放,等到获取锁立即进行处理。但是如何去选择自旋的执行时间呢?如果自旋执行时间太长,会有大量的线程处于自旋状态占用 CPU 资源,进而会影响整体系统的性能。因此自旋周期的选择额外重要。

JVM 对于自选周期的选择,jdk1.5 时这个限度是写死的,在 jdk1.6引入了 适应性自旋锁,适应性自旋锁意味着自旋的时间不再是固定的了,而是 由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间,同时 JVM 还针对当前 CPU 的符合情况做了较多的优化,如果平均负载小于 CPUs(CPU核数)则一直自旋,如果有超过 CPUs/2 (一半的CPU核数)个线程正在自旋,则后来线程直接阻塞,如果正在自旋的线程发现 Owner(当前持有锁的线程)发生了变化则延迟自旋时间(自旋计数)或进入阻塞,如果 CPU 处于节点模式则停止自旋,自旋时间的最坏情况是 CPU 的存储延迟(CPU A 存储了一个数据,到 CPU B 得知这个数据直接的时间差),自旋会适当放弃线程优先级之间的差异。

3.3 自旋锁的开启:

  • JDK1.6 中通过 -XX:+UseSpinning 开启;

    -XX:PreBlockSpin=10 为自旋次数;

  • JDK1.7 后,去掉此参数,由 jvm 控制。


四、Synchronized 同步锁

  • synchronized 它可以把任意一个非 NULL 的对象当作锁。它属于独占式的悲观锁,同时属于可重入锁。

4.1 Synchronized 作用范围:

  1. 作用于 普通方法 时,锁住的时对象的实例(this);
  2. 作用于 静态方法 时,锁住的是 Class 实例,又因为 Class 的相关数据存储在永久代 PermGen(jdk1.8 则是 metaspace),永久代是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程。
  3. 作用于 对象实例 时,锁住是所有以该对象为锁的代码块。它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器(Monitor)会将这些线程存储在不同的容器中。

4.2 Synchronized 核心组件:

1)Wait Set:存储调用 wait() 方法被阻塞的线程。
2)Contention List竞争队列,所有请求锁的线程首先被放在这个竞争队列中。
3)Entry List:Contention List 中那些有资格成为候选资源的线程被移动到 Entry List 中
4)OnDeck:任意时刻,最多只有一个线程正在竞争所资源,该线程被成为 OnDeck
5)Owner:当前已经获取到锁资源的线程被成为 Owner;
6)!Owner:当前释放锁的线程。

4.3 Synchronized 实现:

  1. JVM 每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,ContentionList 会被大量的并发线程进行 CAS 访问,为了降低对尾部元素的竞争,JVM 会将一部分线程移动到 EntryList 中作为候选竞争线程。
  2. Owner 线程会在 unlock 时,将 ContentionList 中的部分线程迁移到 EntryList 中,并指定 EntryList 中的某个线程为 OnDeck 线程(一般是先进去的那个线程)。
  3. Owner 线程并不直接把锁传递给 OnDesk 线程,而是把锁竞争的权利交给 OnDeck,OnDeck 需要重新竞争锁。这样 虽然牺牲了一些公平性,但是能极大地提升系统的吞吐量,在 JVM 中,也把这种选择行为称之为 “竞争切换”。
  4. OnDeck 线程获取到锁资源后会变为 Owner 线程,而没有得到锁资源的仍然停留在 EntryList 中。如果 Owner 线程被 wait 方法阻塞,则转移到 WaitSet 队列中,直到某个时刻通过 notify 或者 notifyAll 唤醒,会重新进去 EntryList 中。
  5. 处于 ContentionList、EntryList、WaitSet 中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux 内核下采用 pthread_mutex_lock 内核函数实现的)。

4.4 Synchronize 补充:

  1. synchronized 是非公平锁。Synchronized 在线程进入 ContentionList 时,等待的线程会先尝试自旋获取锁,如果获取不到就进入 ContentionList,这明显对于已经进入队列的线程是不公平的。还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁资源。
  2. 每个对象都有个 monitor 对象,加锁就是在竞争 monitor 对象,代码块加锁是在前后分别加上 monitorentermonitorexit 指令来实现的,方法加锁是通过一个标记为来判断的。
  3. synchronized 是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线程加锁消耗的时间比有用操作消耗的时间更多。
  4. JDK1.6 中,对 synchronized 锁进行了很多的优化,有适应自旋、锁消除、锁粗化、偏向锁及轻量级锁等,效率有了本质上的提高。在之后推出的 Java1.7 与 1.8 中,均对该关键字的实现机理做了优化,引入了偏向锁和轻量级锁。都是在对象头中有标记为,不需要经过操作系统加锁。
  5. 锁可以从偏向锁升级到轻量级锁,再升级到重量级锁,这种升级过程叫做 锁膨胀,也叫 锁升级
  6. JDK1.6中,默认是开启偏向锁和轻量级锁,可以通过 -XX:-UseBiasedLocking 来禁用偏向锁。

五、ReentractLock 锁

  • ReentrantLock 集成接口 Lock 并实现了接口中定义的方法,他是一种 可重入锁除了能完成 synchronized 所能完成的所有工作外,还提供了诸如可响应终端所、可轮询锁请求、定时锁等避免多线程死锁的方法

5.1 Lock 接口的主要方法:

  1. void lock():执行此方法时,如果锁处于空闲状态,当前线程将获取到锁。相反,如果锁已经被其他线程持有,将禁用当前线程,直到当前线程获取到锁。
  2. boolean tryLock()如果锁可用,则获取锁,并立即返回 true,否则返回 false。该方法和 lock() 的区别在于,tryLock() 只是 “试图” 获取锁,如果锁不可用,不会导致当前线程被禁用,当前线程仍然继续往下执行代码。而 lock() 方法则是一定要获取到锁,如果锁不可用,就一直等待,在未获得锁之前,当前线程并不继续向下执行。
  3. boolean tryLock(long time, TimeUnit unit)如果锁在给定时间内没有被另一个线程保持,则获取该锁。
  4. void unlock():执行此方法时,当前线程将释放持有的锁。锁只能由持有者释放,如果线程并不持有锁,却执行该方法,可能导致异常的发生。
  5. Condition newCondition()条件对象,获取等待通知组件。该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的 await() 方法,而调用后,当前线程将释放锁。
  6. getHoldCount()查询当前线程保持此锁的次数,也就是执行此线程执行 lock 方法的次数。
  7. getQueueLength()返回正等待获取此锁的线程估计数,比如启动 10 个线程,1个线程获得锁,此时返回的是9。
  8. getWaitQueueLength(Condition condition)返回等待与此锁相关的给定条件的线程估计数。比如 10 个线程,用同一个 condition 对象,并且此时这 10 个线程都执行了 condition 对象的 await 方法,那么此时执行此方法返回 10。
  9. hasWaiters(Condition condition)查询是否有线程等待此锁有关的给定条件(condition)。即对于指定 condition 对象,查询有多少线程执行了 condition.await() 方法。
  10. hasQueueThread(Thread thread)查询给定线程是否等待获取此锁
  11. hasQueueThreads()是否有线程等待此锁
  12. isFair()该锁是否为公平锁
  13. isHeldByCurrentThread()当前线程是否保持锁锁定,线程的执行 lock() 方法的前后分别是 false 和 true。
  14. isLock()此锁是否有任意线程占用
  15. lockInterruptibly()如果当前线程未被中断,获取锁

5.2 ReentrantLock 与 synchronized 对比:

  1. ReentrantLock 通过方法 lock()unlock() 来进行加锁与解锁操作, 与 synchronized 结束后会被 JVM 自动解锁的机制不同,ReentractLock 加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用 ReentractLock 必须在 finally 控制块中进行解锁操作。
  2. ReetrantLock 相比 synchronized 的优势是 可中断、公平锁、多个锁。这种情况下需要使用 ReentrantLock。

5.3 Condition 接口

  • Condition 接口是 ReentrantLock 的一部分,它提供了比 Object 监视器方法(如 wait()notify())更高级的线程等待/唤醒机制。Condition 允许更精确地控制线程间的同步,特别是当需要基于特定条件进行等待时。

Condition 接口通常与 ReentrantLock 一起使用,以实现更复杂的线程间协作模式,如:生产者-消费者模式、读者-写者模式等。下面介绍 3 个 Condition 的方法:

  • await():方法会使当前线程进入等待状态,并释放 ReentrantLock 锁,直到其他线程调用了相应的 signal()signalAll() 方法时才会被唤醒。

  • signal():方法用于唤醒一个等待在该 Condition 上的线程。

    (注意:signal() 不会立即唤醒线程,而是将其放入就绪队列,由操作系统调度器决定何时真正唤醒。)

  • signalAll():方法会唤醒所有等待在该 Condition 上的线程。通常在需要所有等待线程都参与接下来的执行流程时使用。

5.4 ReentrantLock 使用示例1:基本使用

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockExample {/*** 初始化锁*/

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

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

相关文章

业务发展中 10 个最佳的 OKR 示例

业务发展是推动组织增长、培养合作伙伴关系和扩大市场覆盖范围的重要职能。目标和关键结果 (OKR) 可以作为推动业务发展工作和实现战略目标的强大工具。在这里,我们展示了业务发展中的十个最佳 OKR 示例,为旨在在该领域脱颖而出并实现其增长目标的组织提…

产品体验周刊第2期(2024-7-8)

产品体验 阿里系产品的营销活动 无论是支付宝,饿了么,咸鱼等,产品的营销活动频次过高,且几乎任何一个活动页都让人无法理解想要表达什么,只有满屏的红包、优惠等字眼,开始对这类活动脱敏也是基于这些产品…

【漏洞复现】方正全媒体采编系统——SQL注入

声明:本文档或演示材料仅供教育和教学目的使用,任何个人或组织使用本文档中的信息进行非法活动,均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现测试工具 漏洞描述 方正全媒体采编系统(FZMediaEditor)是一…

在pycharm中使用jupyter

在pycharm中使用jupyter 前置条件:你的环境中应该有juptyer ,没有的话 pip install jupyter 点击项目目录,右键->new->jupyter notebook 打开file settings 找到 jupyter server (按照默认的用代理服务器就行) P…

大连外贸建站公司wordpress主题模板

Robonaut萝卜纳特WP外贸站模板 适合用于工业机器人公司出口做外贸搭建公司官方网站使用的WordPress模板。 https://www.jianzhanpress.com/?p7091 优衣裳WordPress外贸建站模板 简洁的wordpress外贸独立站模板,适合服装、衣服、制衣外贸公司搭建公司官方网站使用…

python- Flask模块 demo

文章目录 前言python- Flask模块 demo1. 主要特点2. demo 准备工作3. demo 实例4. 测试 前言 如果您觉得有用的话,记得给博主点个赞,评论,收藏一键三连啊,写作不易啊^ _ ^。   而且听说点赞的人每天的运气都不会太差&#xff0c…

前端JS特效第21集:HTML5响应式多种切换效果轮播大图切换js特效代码

HTML5响应式多种切换效果轮播大图切换js特效代码&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下(全部代码在文章末尾)&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-t…

分别通过LS和RML进行模型参数辨识matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 最小二乘法(LS)参数辨识 4.2 递归最大似然估计(RML)参数辨识 5.完整程序 1.程序功能描述 分别通过LS和RML进行模型参数辨识matlab仿真&#xff0c;仿真输出参数辨识的误差&#xff0c…

InvalidVersionSpecError: Invalid version spec: =2.7解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

微软Edge浏览器全解析:从速度到安全性的全面体验

微软Edge浏览器&#xff0c;自2015年首次亮相以来&#xff0c;已经成为了浏览器市场上不可忽视的一股力量。它不仅集成了Windows 10的许多原生功能&#xff0c;还在速度和安全性上进行了大量的优化。本文将全面解析微软Edge浏览器的各项特性&#xff0c;带您领略这款浏览器的魅…

idea 默认路径修改

1.查看 idea 的安装路径&#xff08;右键点击 idea 图标&#xff0c;查看路径 &#xff09; “C:\Program Files\JetBrains\IntelliJ IDEA 2021.3.1\bin\idea64.exe” 在 bin 目录查看 idea.properties 文件&#xff0c;修改以下四个路径文件 # idea.config.path${user.home}/…

超高精电容传感器PCAP01调试+LABVIEW数据可视化调试手记

PCAP01超高精电容传感芯片STM32LabView可视化 文章目录 PCAP01超高精电容传感芯片STM32LabView可视化一、PCAP01介绍1.1、PCAP01引脚定义1.2、电容测量1.3、温度测量1.4、PCAP典型测试电路 二、PCAP01的STM32驱动2.1、SPI协议配置2.2、PCAP01浮空电容测量内部温度测量操作流程 …

Java Swing 5种布局管理器基本示例

在Java Swing中&#xff0c;常用的布局管理器有BorderLayout、FlowLayout、GridLayout、BoxLayout和GridBagLayout。最灵活的是GridBagLayout。 基本使用如下&#xff0c; borderlayout&#xff0c; import javax.swing.*; import java.awt.*;public class borderlay {publi…

yum install epel-release 遇到的问题

问题&#xff1a; 安装epel的时候,执行 yum install -y epel-release 报错“Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&archx86_64&repoos&infrastock error was 14: curl#6 - "Could not resolve host: mirrorlist.centos.…

读人工智能全传06逻辑编程

1. 现代逻辑 1.1. 到了20世纪初&#xff0c;现代逻辑的基本框架已经大致建立起来&#xff0c;当时确立的逻辑运算系统&#xff0c;直至如今仍然能够支撑数学家几乎所有的逻辑推理工作 1.1.1. 这个系统被称为一阶逻辑&#xff0c;一阶逻辑是数学和推理的通用语言 1.1.2. 这个…

知识的向量表示

1、one-hot表示&#xff0c;空间太大 2、bag词袋模型&#xff0c;无法表示词的语义 3、词的语义由什么决定&#xff1f;词由他的上下文决定&#xff1f;分布式语义 4、CBow&#xff0c;通过前面几个词和后面几个词&#xff0c;预测中间几个词 5、skip-gram&#xff0c;通过…

Matlab协方差矩阵分解法生成随机场

Matlab协方差矩阵分解法生成随机场 相关系数矩阵 % function outcohesion(x,y,mu,theta) % end % xyload(F:\Research-OUC\基于机器许学习模型的海底斜坡可靠度研究\基于comsol的斜坡稳定性分析\comsol网格操作\grid_operate-matlab.mphtxt); % xxy(:,1); % yxy(:,2); Xlinspac…

Adobe Premiere Pro 2024 v24.5 (macOS, Windows) - 专业视频编辑软件

Adobe Premiere Pro 2024 v24.5 (macOS, Windows) - 专业视频编辑软件 Acrobat、After Effects、Animate、Audition、Bridge、Character Animator、Dimension、Dreamweaver、Illustrator、InCopy、InDesign、Lightroom Classic、Media Encoder、Photoshop、Premiere Pro、Adob…

【Selenium配置】WebDriver安装浏览器驱动(ChromeEdge)

【Selenium配置】WebDriver安装浏览器驱动&#xff08;Chrome&Edge&#xff09; 文章目录 【Selenium配置】WebDriver安装浏览器驱动&#xff08;Chrome&Edge&#xff09;Chrome确认Chrome版本下载对应driver把解压后的chromedriver文件放在chrome安装目录下&#xff0…

算法简介:什么是算法?——定义、历史与应用详解

引言 在现代计算机科学中&#xff0c;算法是一个核心概念。无论是编程还是数据分析&#xff0c;算法都扮演着至关重要的角色。在这篇博客中&#xff0c;我们将深入探讨算法的定义、历史背景以及它在计算机科学中的地位和实际应用。 什么是算法&#xff1f; 算法是解决特定问题…