【多线程】锁消除、锁粗化、偏向锁、自旋锁、自适应字段锁、轻量级锁、重量级锁

本文主要是介绍【多线程】锁消除、锁粗化、偏向锁、自旋锁、自适应字段锁、轻量级锁、重量级锁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

    • 1. 锁的概念
    • 2. 锁的状态
    • 3. 锁的优化策略
      • 1. 锁消除(编译器的操作)
        • 1. 优点
        • 2. 锁消除的原理(怎么实现的?逃逸分析)
          • 1. 问:JVM如何在编译期消除锁的?
          • 2. 题外话:什么是逃逸分析技术?
          • 3. 问:JVM如何判断对象是否逃逸?(了解就行)
          • 4. 题外话:隐式使用同步方法是什么意思?
          • 5. 有人说,单线程下的对象一定会发生锁消除吗?(好问题,我自己编的)
      • 2. 锁粗化
        • 1. 锁粗化的概念(for循环)
        • 2. 锁粗化出现的场景(vector方法的add操作)
        • 2. 锁粗化的原理
      • 3. 锁升级
        • 1. 锁升级的过程
        • 2. 锁升级的原理
        • 3. 什么时候会发生锁升级?
      • 4. 自旋锁
        • 1. 为什么要引入自旋锁?
        • 2. 自旋锁的定义
        • 3. 自旋锁的优点
        • 4. 自旋锁的缺点
        • 5. 适用场景
        • 6. 使用自旋锁的注意事项
        • 7.JVM相关参数
        • 8. 问:自旋锁有什么缺陷?
      • 5. 自适应自旋锁(自旋次数动态变化)
        • 1. 自旋次数由什么决定?
        • 2. 自适应自旋锁如何体现《自适应》?(JVM的伯乐心理,伯乐这词感觉也不是很恰当,又有点像是:教练海选种子选手)
      • 6. 偏向锁(在单线程环境居多)
        • 1. 什么是偏向锁?
        • 2. 为什么要引入偏向锁?目的是什么?(使单线程快上再加快)
          • 1. 思考题:一个线程执行同步块,那就是不会发生资源并发冲突,就代表着会发生锁消除吗?
        • 3. 偏向锁的原理(一次CAS指令、对象头中的锁偏向线程ID)
          • 1. 获取锁(拿到锁,然后记录在自己对象头中,且不会释放锁)
          • 2. 释放锁
        • 4. 偏向锁什么时候会转化成轻量级锁?(多线程且资源竞争激烈)
          • 1. 题外话:关于资源竞争激烈,具体怎么解释?(这是概念的理解问题,到哪都可以问)
          • 2. 多线程的情况下,一定不存在偏向锁吗?(这问题很深)
        • 5. JVM相关配置
      • 7. 轻量级锁
        • 1. 轻量级锁的出现
        • 2. 优点
        • 3. 轻量级锁的原理(多次CAS指令)(这里有空再理解)
      • 8. 重量级锁(如Synchronized、用户态、内核态)
        • 1. 出现的原因
      • 9.重量级锁、轻量级锁和偏向锁之间转换
        • 1. 三者之间的转换
        • 2. 三者之间的比较

1. 锁的概念

锁消除、锁粗化、偏向锁、轻量级锁

2. 锁的状态

无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态

3. 锁的优化策略

1. 锁消除(编译器的操作)

锁消除是在编译器级别的事情。

编译器时,如果一些对象不可能存在共享资源竞争,JVM会消除这些对象的锁操作

白话文:如果我不使用多线程,那就代表着我使用的这些对象,在编译期时会执行锁消除

1. 优点

节省时间,可以节省毫无意义的请求锁的时间

2. 锁消除的原理(怎么实现的?逃逸分析)

检查变量是否逃逸

  1. 不会锁消除:如果变量逃逸了,如方法使用了(显示或者隐式)synchronized修饰的方法
  2. 会锁消除:如果变量未逃逸,JVM会检测出来,并把它的锁去掉
1. 问:JVM如何在编译期消除锁的?

应该是在编译器生成字节码的时候,JVM阻止它去生成monitorEntermonitorExit两个指令

2. 题外话:什么是逃逸分析技术?

这个我在【八股文】JVM篇有讲,比如方法中的一些对象没有返回或者没有被外部的方法使用,那这些对象就会直接保存在栈中,而不是堆!

3. 问:JVM如何判断对象是否逃逸?(了解就行)

对于虚拟机来说需要使用数据流分析

4. 题外话:隐式使用同步方法是什么意思?

对我们开发人员来说,虽然没有显示使用锁,但是在使用一些JDK的内置API时,如StringBuffer、Vector、HashTable等,这个时候会存在隐形的加锁操作(不就是加了synchronized嘛,搞得这么玄乎。。无语)。比如StringBuffer的append()方法,Vector的add()方法。

5. 有人说,单线程下的对象一定会发生锁消除吗?(好问题,我自己编的)

锁消除是根据对象逃逸分析的,单线程只能代表者不会发生并发冲突,但不代表着对象不会逃逸

2. 锁粗化

1. 锁粗化的概念(for循环)

就是将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁

白话文:比如我对for循环里面的代码块进行了加锁操作,JVM为了提升效率,会直接把锁放到for循环外面,对整个for循环的代码块进行加锁操作

2. 锁粗化出现的场景(vector方法的add操作)

vector每次add的时候都需要加锁操作,JVM检测到对同一个对象(vector)连续加锁、解锁操作,会合并一个更大范围的加锁、解锁操作,即加锁解锁操作会移到for循环之外

2. 锁粗化的原理

3. 锁升级

1. 锁升级的过程

锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级。

2. 锁升级的原理

锁的升级主要都是通过Mark Word中的锁标志位与是否是偏向锁标志位来实现的;

白话文:锁的升级主要是通过:对象头中锁相关的2个标志位(锁标志位偏向锁标志位)实现的

3. 什么时候会发生锁升级?

4. 自旋锁

1. 为什么要引入自旋锁?
  1. 频繁的操作锁对CPU来说吃不消:对线程的阻塞和唤醒操作的实现,本质是CPU切换运行状态用户态内核态),操作太频繁的话CPU也吃不消呀

  2. 锁对象的时间非常短,不值得锁(有种杀鸡焉用牛刀的感觉):为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的

2. 自旋锁的定义

指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

白话文:尝试去获取锁,如果锁被占用,则循环检测,直到获取锁(应该有个最大检测时间吧)

3. 自旋锁的优点
  1. 避免线程切换带来的开销

白话文:我把CPU占用着,用完才释放

4. 自旋锁的缺点
  1. 占用CPU的处理时间,所以尽可能操作粒度小,否则会有种占着CPU不拉屎的感觉
5. 适用场景
  1. 适用于锁保护的临界区很小的情况(粒度小的)
6. 使用自旋锁的注意事项
  1. 保证自旋粒度小
  2. 需要设置最大循环次数
7.JVM相关参数
  • -XX:+UseSpinning:在JDK 1.4.2中引入,默认关闭自旋,用这个命令开启自旋
  • -XX:PreBlockSpin:JDK1.6中默认开启,且默认自旋10次,用这个命令修改自旋次数

白话文:我开启自旋,具体体现在哪里呢?是所有的对象监视器锁都采用了自旋锁??

8. 问:自旋锁有什么缺陷?

例子:线程A占用锁,线程B自旋10次未获取到锁,然后放弃了,不巧的是,线程B前脚刚走,线程A就好了,把锁释放了,呀!好尴尬呀。。

解决:JDK1.6引入自适应的自旋锁,夸一下,JDK1.6牛皮!

5. 自适应自旋锁(自旋次数动态变化)

1. 自旋次数由什么决定?

前一次在同一个锁上的自旋时间锁的拥有者的状态来决定

白话文:参考上次的自旋时间

2. 自适应自旋锁如何体现《自适应》?(JVM的伯乐心理,伯乐这词感觉也不是很恰当,又有点像是:教练海选种子选手)

线程如果自旋成功了,那么下次自旋的次数会更加多,因为JVM认为既然上次成功了,下次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。

白话文:JVM像是一个伯乐,如果有天赋(成功自旋一次),JVM会给你更多资源(更多的自旋次数),尽可能地保证你次次都自旋成功

反之,如果对于某个锁,很少有自旋能够成功,那么在以后要获取这个锁的时候自旋的次数会减少甚至省略掉自旋过程(为了省资源)

白话文:如果你没有天赋(很少能自旋成功),JVM会减少给你提供的资源,甚至不给你资源,活活饿死(别自旋了,阻塞去吧!)

6. 偏向锁(在单线程环境居多)

JDK1.6的时候引入的

1. 什么是偏向锁?

偏向锁是在单线程执行代码块时使用的机制,如果在多线程并发的环境下,则一定会转化为轻量级锁或者重量级锁。

2. 为什么要引入偏向锁?目的是什么?(使单线程快上再加快)

在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得(是因为设置了线程的优先级吗?不对线程的优先级是针对获取CPU调度而言的,和获取锁的优先级维度不一样,继续继续。。),为了让线程获得锁的代价更低(说的有点抽象了,兄dei),引进了偏向锁

白话文:在只有一个线程执行同步块进一步提高性能

1. 思考题:一个线程执行同步块,那就是不会发生资源并发冲突,就代表着会发生锁消除吗?

错了,锁消除是根据对象逃逸分析的,不是通过判断资源是否有并发冲突

3. 偏向锁的原理(一次CAS指令、对象头中的锁偏向线程ID)
1. 获取锁(拿到锁,然后记录在自己对象头中,且不会释放锁)

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向线程ID,以后该线程进入和退出同步块时不需要花费CAS操作来争夺锁资源,只需要检查是否为偏向锁锁标识为以及ThreadID即可

白话文:一个线程第一次进入同步块,会在对象头中记录锁偏向线程ID,下次再访问的时候,如果还是偏向锁,就会先判断是不是这个锁偏向线程ID,是的话就直接执行同步代码块

2. 释放锁

偏向锁的释放采用了 一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争。偏向锁的撤销需要 等待全局安全点(这个时间点是上没有正在执行的代码)

当发生线程竞争资源时:

  1. 暂停拥有偏向锁的线程

  2. 判断锁对象是否还处于被锁定状态(看看偏向锁的这个线程还在不在用):

    恢复到无锁状态(01),允许其他线程竞争,谁抢到,谁就是新的轻量级锁

    升级为轻量级锁状态(00),进入轻量级锁的竞争模式

在这里插入图片描述

图片链接:https://www.cnblogs.com/aspirant/p/11470858.html

图片最下面那部分不太准备,看我上面撤销锁的文字部分好了

4. 偏向锁什么时候会转化成轻量级锁?(多线程且资源竞争激烈)

多线程情况下,出现资源竞争比较激烈的时候,就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗也必须小于接下来的CAS原子指令的性能消耗

白话文:多线程且资源竞争不激烈的情况下

1. 题外话:关于资源竞争激烈,具体怎么解释?(这是概念的理解问题,到哪都可以问)

即线程A尚未执行完同步代码块线程B发起了申请锁的申请

2. 多线程的情况下,一定不存在偏向锁吗?(这问题很深)

不一定,如果资源竞争不激烈的话,还是继续用偏向锁

白话文:偏向锁处理的时间 < 偏向锁转成轻量级锁的时间

在 JDK 1.6 中默认是开启偏向锁轻量级锁

5. JVM相关配置
  • -XX:-UseBiasedLocking:禁用偏向锁(jdk1.6默认是开启的,应该是设置为false代表禁止)

7. 轻量级锁

1. 轻量级锁的出现

关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁,则会尝试获取轻量级锁

2. 优点
  1. 减少传统的重量级锁使用操作系统互斥量产生的性能消耗
  2. 靠多次CAS实现的,效率高
3. 轻量级锁的原理(多次CAS指令)(这里有空再理解)
  1. 在线程进入同步块时,如果同步对象锁状态为无锁状态,JVM在栈帧中创建锁记录空间(Lock Record),此时线程堆栈与对象头的状态如下图所示:
  2. 把对象头中的MarkWord复制到锁记录空间
  3. 拷贝成功后,JVM将使用CAS操作尝试将对象MarkWord中的LockWord更新为指向当前线程LockRecord的指针
    1. 如果CAS成功:把复制过来的MarkWord,更新回对象的MarkWord,这样就获取到对象的轻量级锁了
    2. 如果CAS失败:自旋执行,自旋结束时仍未获得锁,膨胀为重量级锁

在这里插入图片描述
图片来源:https://www.cnblogs.com/aspirant/p/11470858.html

8. 重量级锁(如Synchronized、用户态、内核态)

Synchronized是通过对象内部的一个叫做 监视器锁(Monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到内核态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为 “重量级锁”。

1. 出现的原因

如果线程通过一定次数的CAS尝试没有成功,则进入重量级锁

9.重量级锁、轻量级锁和偏向锁之间转换

1. 三者之间的转换

在这里插入图片描述
图片来源:https://www.cnblogs.com/aspirant/p/11470858.html

2. 三者之间的比较

在这里插入图片描述
图片来源:https://www.cnblogs.com/aspirant/p/11470858.html

这篇关于【多线程】锁消除、锁粗化、偏向锁、自旋锁、自适应字段锁、轻量级锁、重量级锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/243771

相关文章

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

浅析Rust多线程中如何安全的使用变量

《浅析Rust多线程中如何安全的使用变量》这篇文章主要为大家详细介绍了Rust如何在线程的闭包中安全的使用变量,包括共享变量和修改变量,文中的示例代码讲解详细,有需要的小伙伴可以参考下... 目录1. 向线程传递变量2. 多线程共享变量引用3. 多线程中修改变量4. 总结在Rust语言中,一个既引人入胜又可

C#中图片如何自适应pictureBox大小

《C#中图片如何自适应pictureBox大小》文章描述了如何在C#中实现图片自适应pictureBox大小,并展示修改前后的效果,修改步骤包括两步,作者分享了个人经验,希望对大家有所帮助... 目录C#图片自适应pictureBox大小编程修改步骤总结C#图片自适应pictureBox大小上图中“z轴

使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)

《使用Java解析JSON数据并提取特定字段的实现步骤(以提取mailNo为例)》在现代软件开发中,处理JSON数据是一项非常常见的任务,无论是从API接口获取数据,还是将数据存储为JSON格式,解析... 目录1. 背景介绍1.1 jsON简介1.2 实际案例2. 准备工作2.1 环境搭建2.1.1 添加

多线程解析报表

假如有这样一个需求,当我们需要解析一个Excel里多个sheet的数据时,可以考虑使用多线程,每个线程解析一个sheet里的数据,等到所有的sheet都解析完之后,程序需要提示解析完成。 Way1 join import java.time.LocalTime;public class Main {public static void main(String[] args) thro

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法

消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法   消除安卓SDK更新时的“https://dl-ssl.google.com refused”异常的方法 [转载]原地址:http://blog.csdn.net/x605940745/article/details/17911115 消除SDK更新时的“

Java 多线程概述

多线程技术概述   1.线程与进程 进程:内存中运行的应用程序,每个进程都拥有一个独立的内存空间。线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换、并发执行,一个进程最少有一个线程,线程实际数是在进程基础之上的进一步划分,一个进程启动之后,进程之中的若干执行路径又可以划分成若干个线程 2.线程的调度 分时调度:所有线程轮流使用CPU的使用权,平均分配时间抢占式调度