Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >synchronized的偏向、轻量、重量级锁

synchronized的偏向、轻量、重量级锁

作者头像
青山师
发布于 2023-05-05 12:13:11
发布于 2023-05-05 12:13:11
263017
代码可运行
举报
运行总次数:17
代码可运行

synchronized的偏向、轻量、重量级锁

Synchronized实现同步的方式有三种:偏向锁、轻量级锁、重量级锁。本文会从理论和代码实践两方面阐述三种锁的实现细节和原理。

偏向锁

偏向锁的思想很简单,就是偏向于第一个获取锁的线程,当其他线程要获取锁时,会在CAS操作中失败,然后挂起等待,直到第一个线程释放锁。这个锁的好处是可以满足大多数同步场景下的需求,并且消耗很小的资源。

要开启偏向锁,需要添加JVM参数-XX:+UseBiasedLocking

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class BiasedLocking {
    private Object lock = new Object();

    public void method1() {
        synchronized (lock) {
            // do something...
        }
    }
} 

当第一个线程进入synchronized块时,会将锁的标记从none修改为bias状态,同时记录偏向的线程ID。之后其他线程要获取锁,会通过CAS操作尝试将锁偏向自己,但这个操作会失败,所以只会短暂地竞争,很快其他线程就会进入阻塞状态,释放CPU时间片。

当偏向的线程退出同步块时,如果发现锁还没有其他线程在等待,那么会将锁的状态重置为none。如果发现有其他线程在等待,会释放锁,让等待线程获取。

当偏向的线程退出同步块时,如果发现锁还没有其他线程在等待,那么会将锁的状态重置为none。如果发现有其他线程在等待,会释放锁,让等待线程获取。

轻量级锁

轻量级锁的获取过程是通过CAS操作完成的。当前线程会先在对象头中记录自己,然后尝试用CAS将对象头中的锁记录替换为当前线程,如果成功就获取到锁,失败就进入阻塞队列等待唤醒。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class LightWeightLock {
    private Object lock = new Object();

    public void method1() {
        synchronized (lock) {
            // do something...
        }
    }
}

当第一个线程进入方法时,会将对象头中的锁状态修改为当前线程ID,然后进入同步块。其他线程要获取锁时,会先检查对象头的锁状态,如果发现锁已经被占用,那么会使用CAS操作进行抢占,如果成功则获取到锁,失败会加入到阻塞队列进行等待。

当前线程退出同步块时,会使用CAS操作释放锁,将对象头设置为unlocked状态,同时唤醒阻塞队列中的一个等待线程。

轻量级锁的优点是消耗资源小,对代码性能的影响小,但是在高并发的场景下,CAS操作的 ABA问题会导致线程无法正常工作,所以当锁重入超过10次,或者锁持有时间超过1s时,JVM会将轻量级锁升级为重量级锁。

重量级锁

重量级锁会导致当前拥有锁的线程和其他等待线程都进入阻塞状态,切换到内核态,这 obviously 是一个非常消耗资源的操作。

其实现过程是:当前线程首先会在对象头中记录自己,然后进入内核态被阻塞,同时其他线程也会被阻塞。当其中一个线程退出同步块时,会唤醒其他线程中的一个

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class HeavyWeightLock {
    private Object lock = new Object();

    public void method1() {
        synchronized (lock) {
            // do something...
        }
    }
}

当第一个线程进入同步块时,会标记对象头表示此对象处于锁定状态,然后进入内核态挂起。其他线程要获取锁时,会发现对象头的锁定状态,也会进入内核态挂起。

当锁定的线程退出同步块时,会标记对象头为解锁状态,然后唤醒一个等待线程。被唤醒的线程会重新标记对象头为锁定状态,然后继续执行同步块中的内容。

重量级锁的优点是可以解决轻量级锁中的ABA问题,但是其性能消耗也是最大的。所以如果一个锁仅被一个线程使用,或有很高的重入概率,那么应选择偏向锁或轻量级锁,可以获得更高的性能。

偏向锁适用于单线程环境,性能最高;轻量级锁通过CAS实现,性能较好,但是会出现ABA问题;

操作步骤

要实际观察Synchronized锁的三种状态转换,可以使用JDK自带的JMC(Java Mission Control)工具。

下面是具体的操作步骤:

  1. 启动JMC,打开“标记对象(Mark Objects)”功能。
  2. 设置需要追踪的对象,这里我们选择上面的Demo对象。
  3. 运行代码,多线程访问这个对象的同步方法。
  4. JMC中打开“标记对象(Mark Objects)”视图,可以观察到对象头的状态在变化:
    • 初始为none状态,表示无锁
    • 第一个线程进入同步块后变为biased状态,表示偏向锁定
    • 多线程访问后变为轻量级锁,对象头记录为线程ID
    • 重入超过10次或持有超过1s后,变为重量级锁,对象头记录为锁定状态
  5. 当线程退出同步块后,可以观察到锁的释放过程
    • 偏向锁会重置为none状态
    • 轻量级锁使用CAS设置为unlocked状态,并唤醒后继线程
    • 重量级锁设置对象头为unlocked状态,并唤醒后继线程
  6. 可以尝试提高并发量或设置不同的超时时间,观察偏向锁和轻量级锁什么情况下会升级为重量级锁

通过上述步骤,我们可以直观的观察Synchronized锁的三种状态之间的切换过程,这也是理解其原理的最佳途径。

运维实施

在实际项目中,我们如何根据场景选择和设置合适的锁机制呢?这里提供一些参考建议:

  1. 偏向锁:默认开启,适用于大多数轻 contention 的场景,可以通过-XX:-UseBiasedLocking关闭。
  2. 轻量级锁:默认开启,无需配置,在大部分场景下可以获得不错的性能,如果出现ABA问题,会自动升级到重量级锁。
  3. 重量级锁:无需配置,在以下场景会自动使用:
    • 偏向锁或轻量级锁升级
    • JVM发现轻量级锁CAS操作次数过高
    • 同步块内耗时较长(默认超过1s)
  4. 锁消除:可以通过-XX:+EliminateLocks开启,JVM会分析代码,消除不可能存在竞争的锁,以提高性能。
  5. 锁粗化:可以通过-XX:+DoEscapeAnalysis 开启,JVM会分析代码,将多个连续的加锁操作锁合并为一个,以减少加锁操作次数。
  6. 自旋锁:可以通过-XX:PreBlockSpin设置自旋次数,指令在获取锁失败先自旋一定次数,再进入阻塞状态,以减少线程切换。但是如果自旋太长,会消耗CPU,需要根据场景设置。
  7. 锁定超时:可以通过-XX:MonitorTimeout=x设置重量级锁定超时时间,以避免线程因锁定过长出现死锁现象。

除上述JVM层面的设置外,在代码层面我们也可以根据场景选择不同的锁来提高性能,比如使用ReentrantLock代替Synchronized等。

面试相关

理解Synchronized锁及其实现原理,是Java后端工程师的基础知识之一,这部分内容在面试中也常会涉及,下面是一些可能的面试题:

  1. Synchronized的作用是什么?它的实现原理是什么? 答:Synchronized关键字实现同步,使得运行在同一进程中的多个线程操作同步代码段(或方法)时是互斥的。它的实现方式有三种:偏向锁、轻量级锁、重量级锁。具体可以参考本文前述内容。
  2. 偏向锁、轻量级锁、重量级锁的优缺点分别是什么? 可以参考本文“总结”部分的内容。偏向锁资源消耗最少,单线程场景使用;轻量级锁性能较好,使用CAS实现,存在ABA问题;重量级锁性能最差但安全,用于阻塞线程和处理ABA问题。
  3. Synchronized如何进行锁升级? 当偏向锁被不同线程获取超过20次,或轻量级锁被不同线程获取超过10次、或持有时间超过1s,Synchronized会进行锁升级。升级规则如下:
    • 偏向锁升级为轻量级锁
    • 轻量级锁升级为重量级锁
    • 重量级锁不会再降级

    锁升级的目的是为了提高并发性能。偏向锁适用于单线程,升级为轻量级锁可以适应更高的并发;轻量级锁使用CAS有性能损耗,升级为重量级锁可以解决该问题。

  4. JDK1.6之前的Synchronized如何实现?现在的实现方式有何不同? 答:JDK1.6之前,Synchronized只有一种实现方式:重量级锁。 JDK1.6之后,引入了偏向锁和轻量级锁,实现方式更加灵活,可以根据场景选择,大大提高了并发性能。重量级锁会让线程进入阻塞状态,拥有锁的线程与其他线程都阻塞在内核态,资源消耗大。偏向锁和轻量级锁让线程可以在用户态取得锁,资源消耗小,性能更好。所以现代JDK的Synchronized实现方式相比以前更加智能化,可以根据实际场景选择合适的锁,以obtian更好的并发性能。
  5. 是否应该使用Synchronized优先于ReentrantLock?两者的区别是什么? 答:这两个都是可重入锁,用于实现同步功能。 Synchronized是JVM实现的,性能稍差但使用简单,自动释放锁,不会出现死锁风险。 ReentrantLock是JDK实现的,性能更好,可以设置公平锁、锁定超时时间等,但是使用不当会造成死锁,并且需要手动释放锁,否则可能导致资源泄漏。 所以是否应该优先使用,需要根据实际场景决定:
    • 简单同步场景,没有特殊要求,使用Synchronized简单可靠
    • 追求高性能或需要设置特殊锁策略,使用ReentrantLock
    • 资源竞争激烈,且同步块时间长,使用ReentrantLock并设置锁定超时时间可以避免死锁

    两者的主要区别如下:

    • Synchronized是隐式锁(内置语法),ReentrantLock是显式锁(API)
    • Synchronized无法设置锁定超时,ReentrantLock可以
    • Synchronized粗暴地让线程阻塞在内核态,ReentrantLock可以先自旋再阻塞
    • Synchronized适合少量同步代码段,ReentrantLock可以同时锁定多个同步资源
  6. 对锁的理解?锁主要有哪几种? 答:锁是用于实现同步的机制,保证共享资源被线程排他地访问。主要有以下几种锁:
    • 偏向锁:锁定一次后,后续的锁定由同一线程完成,适用于单线程或同一线程重复加锁的场景
    • 轻量级锁:使用CAS操作进行加锁,性能好但存在ABA问题,用于短期加锁
    • 重量级锁:进入内核态阻塞,其他线程也阻塞,性能差但安全,用于长期加锁
    • 自旋锁:获取锁失败后先自旋一定次数再阻塞,适用于锁定时间很短的场景,可以减少线程切换开销
    • 可重入锁:同一线程可以多次获取同一把锁,Synchronized和ReentrantLock都是可重入锁
    • 读写锁:读锁可以被多个线程同时获取,写锁是排他锁,在追求读写并发场景使用
    • 死锁:两个或两个以上线程分别占有一部分资源并等待其他资源,导致无限期等待,需要避免出现
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-05-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
习题3:数字和数学计算
数学运算符号: 1.    +    加号(plus) 2.    -     减号(minus) 3.    /     斜杠(slash) 也叫除号 4.    *     星号(asterisk)也叫乘号 5.    %    百分号(percent)也叫求于 6.    <    小于号(less-than) 7.    >    大于号(greater-than) 8.    <=   小于等于号(less-than-equal) 9.    >=   大于等于号(greater-than-equ
py3study
2018/08/02
4610
html 转义字符常用对照表
字符 十进制 转义字符 描述 @ &#64 &commat at 符号 © &#169 &copy 版权符号 ! &#33 &excl 感叹号 ¡ &#161 &iexcl 倒置感叹号 : &#58 &colon 冒号 , &#44 &comma 逗号 · &#183 &middot 中间点 ‘ &#39 &apos 撇号 ‘ &#8216 &lsquo 左单引号 “ &#8220 &ldquo 左双引号 ( &#40 &lpar 左括号 [ &#91 &lsqb 左方括号 { &#123 &lcub 左花
Savalone
2020/03/13
23.7K0
HTML常用符号
大家好,又见面了,我是全栈君 HTML转义符号 HTML常用符号: 显示一个空格 &nbsp; &#160; < 小于 &lt; &#60; > 大于 &gt; &#62; & &符号
全栈程序员站长
2022/07/15
3.3K0
写作必备的Typora及Markdown语法介绍
” Markdown 是一种轻量级标记语言,创始人是约翰·格鲁伯(John Gruber)。它允许人们 “使用易读易写的纯文本格式编写文档,然后转换成有效的 HTML 文档。”
Java旅途
2021/06/08
5040
程序员英语学习(二) 标点符号对应的英语单词汇总
作为程序员和各种标点字符打交道基本是每天都会经历的,但是不会用英语描述出来也是挺尴尬的,所以我这边汇总以下常用的标点符号,不一定最全,但一定是最符合程序员常用的。
冬夜先生
2021/09/13
1.1K0
HTML转义字符:xss攻击与HTML字符的转义和反转义
转义只是防止xss攻击的一种手段之一,更多请查看:《web开发前端安全问题总结——web前端安全问题汇总》
周陆军博客
2022/06/25
12.2K0
C语言常用的知识没多少之运算符与表达式
在现实中学完数数就要开始学习数的运算,如加减乘除等。C语言也有对数的运算,有算数运算、赋值运算、关系运算、逻辑运算、位运算、条件运算、逗号运算、sizeof运算。在此按照顺序讲解一些常用的运算。
用户5935416
2019/08/01
6350
C语言常用的知识没多少之运算符与表达式
数学建模~~多目标规划
(2)使用上面的这个题目作为例子,简单的翻译一下题干,这个题目说的就是 有1,2这两种产品需要我们进行生产,一共有11的原材料,10小时的时间,产品1的生产消耗原材料2千克,需要耗费1小时,利润是8万元,产品二以此类推;
阑梦清川
2025/02/24
880
数学建模~~多目标规划
【编译原理】词法分析:C/C++实现
编译原理是计算机科学领域的一个重要分支,它研究如何将高级编程语言的源代码转化成计算机能够执行的机器代码或中间代码的过程。编译原理涵盖了编译器的设计和实现,其中编译器是一种将源代码翻译成目标代码的软件工具。编译器的主要任务包括语法分析、词法分析、语义分析、优化和代码生成等环节。
SarPro
2024/02/20
1.9K0
【编译原理】词法分析:C/C++实现
MarkDown 常用语法
最为常用的格式,只需要在文本前面加上 # 即可,同理、你还可以增加二级标题、三级标题、四级标题、五级标题和六级标题,总共六级,只需要增加 # 即可,标题字号相应降低
GoodTime
2024/03/05
1790
MarkDown 常用语法
最常见的Python面试题&答案
ython新手在谋求一份Python编程工作前,必须熟知Python的基础知识。编程网站DataFlair的技术团队分享了一份2018年最常见Python面试题合集,既有基本的Python面试题,也有高阶版试题来指导你准备面试,试题均附有答案。面试题内容包括编码、数据结构、脚本撰写等话题。
程序员鑫港
2021/12/22
8590
Java基础学习笔记二 Java基础语法
对于单行和多行注释,被注释的文字,不会被JVM解释执行;对于文档注释,可以被JDK提供的工具javadoc 所解析,生成一套以网页文件形式体现的该程序的说明文档;单行注释可以嵌套使用,多行注释不能嵌套使用。
緣來
2020/01/02
6220
Java基础学习笔记二 Java基础语法
PL/0语言编译程序分析
  PL/0语言是Pascal语言的一个子集,我们这里分析的PL/0的编译程序包括了对PL/0语言源程序进行分析处理、编译生成类PCODE代码,并在虚拟机上解释运行生成的类PCODE代码的功能。   PL/0语言编译程序采用以语法分析为核心、一遍扫描的编译方法。词法分析和代码生成作为独立的子程序供语法分析程序调用。语法分析的同时,提供了出错报告和出错恢复的功能。在源程序没有错误编译通过的情况下,调用类PCODE解释程序解释执行生成的类PCODE代码。   词法分析子程序分析:   词法分析子程序名为get
逍遥剑客
2018/05/21
1.8K0
可以直接用于HTML中的特殊字符表 unicode字符集
目录 箭头类 基本形状类 货币类 数学类 音乐符号类 对错号 全都是星星 星座类 国际象棋类 扑克牌类 希腊字母 十字 法律符号 标点和符号 ⇠ 箭头类 符号UNICODE符号UNICODEHTMLJSCSSHTMLJSCSS⇠&#8672u21E021E0⇢&#8674u21E221E2⇡&#8673u21E121E1⇣&#8675u21E321E3↞&#8606u219E219E↠&#8608u21A021A0↟&#8607u219F219F↡&
拿我格子衫来
2022/01/24
2.9K0
python基础语法(1)
从今天起,将进行python的一个系列学习,从基本的语法学起,后期会推出一些关于web开发,网络爬虫以及用python的第三方库进行数据挖掘与机器学习等高级的开发应用,敬起期待~欢迎转发 Python的特点   1. 简单     Python是一种代表简单思想的语言。   2. 易学     Python有极其简单的语法。   3. 免费、开源     Python是FLOSS(自由/开放源码软件)之一。   4. 高层语言     使用Python编写程序时无需考虑如何管理程序使用的内存一类的底层细节。
昱良
2018/04/04
1.1K0
Python 基础语法(一)「建议收藏」
    Python已被移植到很多平台,这些平台包括Linux、Windows、FreeBSD、Macintosh、Solaris、OS/2、Amiga、AROS、AS/400、
全栈程序员站长
2022/09/07
6310
6.QT-简易计算器实现(详解)
界面展示 image.png image.png image.png 1.用户界面类设计 需要使用QWidget组件作为顶层窗口,QLineEdit组件作为输入框,QPsuhButton作为按钮 1.1 在代码里处理按键消息时,需要处理下用户输入的格式(方便逻辑模块计算) 1)匹配括号成对出现,左括号必然先于右括号出现 当有左括号出现时,则status++ 当有右括号出现时,并且status!=0时,则右括号有效,并status--   2)判断每个按键是否合法 数字前面不能为:右括号 比如: 10+
诺谦
2018/05/28
2.4K0
MarkDown表情符号码与基本语法
Emoji表情 将对应emoji表情的符号码复制后输入你的markdown文本即可显示emoji表情。 如:blush:,显示为😊 人物 syntax preview syntax preview syntax preview :bowtie: :bowtie: :smile: 😄 :laughing: 😆 :blush: 😊 :smiley: 😃 :relaxed: ☺️ :smirk: 😏 :heart_eyes: 😍 :kissing_heart: 😘 :kissing_closed_eyes: 😚
MinChess
2022/12/26
3.6K0
[Mac 技巧]如何在Mac OS X里输入特殊字符?
常见的特殊符号,在mac中怎么输入?见下面: 苹果标志 (Shift+Option+K) Copyright © (Option+G) 美元 $ (Shift+4) 美分 ¢ (Option+4) 英镑 £ (Option+3) 日元 ¥(Option+Y) 欧元 €(Shift+Option+2) 破折号 –(Option+-) 约等于 ≈(Option+X) 度 °(Shift+Option+8) 除号 ÷(Option+/) 循环 ∞(Option+5) 小于等于≤(Option+,) 大于等于≥(
EltonZheng
2021/01/26
8360
常用特殊符号的HTML代码(HTML字符实体)
ISO-8859-1 的较低部分(从 1 到 127 之间的代码)是最初的 7 比特 ASCII。
用户5640963
2019/07/26
10.2K0
相关推荐
习题3:数字和数学计算
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验