前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >synchronized 底层如何实现?什么是锁升级、降级?

synchronized 底层如何实现?什么是锁升级、降级?

作者头像
王小明_HIT
发布于 2020-05-08 10:12:01
发布于 2020-05-08 10:12:01
1.5K00
代码可运行
举报
文章被收录于专栏:程序员奇点程序员奇点
运行总次数:0
代码可运行

synchronized 底层如何实现?什么是锁升级、降级?

synchronized 代码块是由一对 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实现单元。

  • https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#d5e13622

在Java6之前, Monitor的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。现代的( Oracle)JDK中,JVM对此进行了大刀阔斧地改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜锁( Biased Locking)轻量级锁重量级锁,大大改进了其性能。

什么是锁升级,降级?

所谓的锁升级、降级,就是 JVM 优化 synchronized 运行的机制,当 JVM 监测到不同的竞争状况是,会自动切换到不同的锁实现。这种切换就是锁的升级、降级。

对象的结构

说偏向锁之前,需要理解对象的结构,对象由多部分构成的,对象头,属性字段、补齐区域等。所谓补齐区域是指如果对象总大小不是4字节的整数倍,会填充上一段内存地址使之成为整数倍。

image

偏向锁又和对象头密切相关,对象头这部分在对象的最前端,包含两部分或者三部分:Mark Words、Klass Words,如果对象是一个数组,那么还可能包含第三部分:数组的长度。

  • Klass Word里面存的是一个地址,占32位或64位,是一个指向当前对象所属于的类的地址,可以通过这个地址获取到它的元数据信息。
  • Mark Word需要重点说一下,这里面主要包含对象的哈希值、年龄分代、hashcode、锁标志位等。

如果应用的对象过多,使用64位的指针将浪费大量内存。64位的JVM比32位的JVM多耗费50%的内存。我们现在使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。

以64位操作系统为例,对象头存储内容图例

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
|--------------------------------------------------------------------------------------------------------------|
|                                              Object Header (128 bits)                                        |
|--------------------------------------------------------------------------------------------------------------|
|                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |
|--------------------------------------------------------------------------------------------------------------|
|  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  无锁
|----------------------------------------------------------------------|--------|------------------------------|
|  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                                                                      | lock:2 |     OOP to metadata object   |    GC
|--------------------------------------------------------------------------------------------------------------|

对象头中的信息如何理解呢,举个例子

image

从该对象头中分析加锁信息,MarkWordk为0x0000700009b96910,二进制为0xb00000000 00000000 01111111 11110000 11001000 00000000 01010011 11101010。倒数第三位为"0",说明不是偏向锁状态,倒数两位为"10",因此,是重量级锁状态,那么前面62位就是指向互斥量的指针。

basied_lock

lock

状态

0

01

无锁

1

01

偏向锁

0

00

轻量级锁

0

10

重量级锁

0

11

GC标记

  • age:Java GC标记位对象年龄。
  • identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。
  • thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。
  • epoch:偏向时间戳。
  • ptr_to_lock_record:指向栈中锁记录的指针。
  • ptr_to_heavyweight_monitor:指向线程Monitor的指针。

无锁

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  A a = new A();
  System.out.println(ClassLayout.parseInstance(a).toPrintable());

可以看到最后 00000001 basied_lock = 0, lock =01 表示无锁

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
JavaThread.synchronizestructure.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

偏斜锁

当没有竞争出现时,默认使用偏斜锁。JVM 会利用 CAS 操作在对象头上的 Mark Word 部分设置线程 ID ,以表示对象偏向当前线程。所以并不涉及真正的互斥锁,这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竟争开销。测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Thread.sleep(5000);
A a = new A();
synchronized (a) {
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
}

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
JavaThread.synchronizestructure.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 28 8d 02 (00000101 00101000 10001101 00000010) (42805253)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

00000101 中 basied_lock = 1, lock =01 表示偏斜锁

轻量级锁

thread1中依旧输出偏向锁,主线程获取对象A时,thread1虽然已经退出同步代码块,但主线程和thread1仍然为锁的交替竞争关系。故此时主线程输出结果为轻量级锁。测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Thread.sleep(5000);
A a = new A();

Thread thread1= new Thread(){
  @Override
  public void run() {
      synchronized (a){
          System.out.println("thread1 locking");
          System.out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向锁
      }
  }
};
thread1.start();
thread1.join();
Thread.sleep(10000);

synchronized (a){
  System.out.println("main locking");
  System.out.println(ClassLayout.parseInstance(a).toPrintable());//轻量锁
}
}

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
JavaThread.synchronizestructure.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 40 e9 19 (00000101 01000000 11101001 00011001) (434716677)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c0 00 20 (10100000 11000000 00000000 00100000) (536920224)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

main locking
JavaThread.synchronizestructure.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           38 f6 c7 02 (00111000 11110110 11000111 00000010) (46659128)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c0 00 20 (10100000 11000000 00000000 00100000) (536920224)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

00000101 依然是偏向锁,00111000 是轻量级锁

重量级锁

thread1 和 thread2 同时竞争对象a,此时输出结果为重量级锁

测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Thread.sleep(5000);
A a = new A();
Thread thread1 = new Thread(){
    @Override
    public void run() {
        synchronized (a){
            System.out.println("thread1 locking");
            System.out.println(ClassLayout.parseInstance(a).toPrintable());
            try {
                //让线程晚点儿死亡,造成锁的竞争
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
};
Thread thread2 = new Thread(){
    @Override
    public void run() {
        synchronized (a){
            System.out.println("thread2 locking");
            System.out.println(ClassLayout.parseInstance(a).toPrintable());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
};
thread1.start();
thread2.start();

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
thread2 locking
JavaThread.synchronizestructure.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           7a f5 99 17 (01111010 11110101 10011001 00010111) (395965818)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           62 c1 00 20 (01100010 11000001 00000000 00100000) (536920418)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

01111010 basied_lock = 0 lock=10 重量级锁

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员奇点 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
51Nod-1232-完美数
该文是关于计算最小公倍数的一种算法实现,通过记录每个数字出现的次数,使用数位dp的方法进行求解,最后返回最小公倍数。
f_zyj
2018/01/09
5570
51Nod-1232-完美数
频频和省选、ICPC 区域赛撞题的 POI ,是何方神圣?
POI 是波兰高中生信息学全国竞赛,曾经和不少省选和 ICPC 区域赛撞过题,当然 POI 的题目出现的是更早。
ACM算法日常
2021/10/27
7680
洛谷P4103 [HEOI2014]大工程(虚树 树形dp)
任意两点的路径和可以考虑每条边两边的贡献,\(d[x]\)表示到该节点的所有节点的路径和,转移的时候考虑一下两棵子树的siz就行(画一下图就很清楚了)
attack
2019/03/20
4650
HPU 18级个人积分赛--first
J. Worker 思路:我们仔细分析一下题意,给了n个厂,m个人,假设每个厂的福利原因使得工人的工作能力不同,然后你要把这m个人分给n个厂,使得每个厂的总效益相同。给厂分人,肯定是福利不好效益低的厂多分几个人,然后效益高的人少。如何实现这个决策呢?***求最小公倍数!!!***所有厂效益的最小公倍数,然后用这个最小公倍数挨个求得是每个厂效益的多少倍并累加。如果工人数对这个倍数取余 == 0;则说明可以实现这样的一种分配!
杨鹏伟
2020/09/11
3340
HPU 18级个人积分赛--first
HDU3949 XOR(线性基第k小)
XOR is a kind of bit operator, we define that as follow: for two binary base number A and B, let C=A XOR B, then for each bit of C, we can get its value by check the digit of corresponding position in A and B. And for each digit, 1 XOR 1 = 0, 1 XOR 0 = 1, 0 XOR 1 = 1, 0 XOR 0 = 0. And we simply write this operator as ^, like 3 ^ 1 = 2,4 ^ 3 = 7. XOR is an amazing operator and this is a question about XOR. We can choose several numbers and do XOR operatorion to them one by one, then we get another number. For example, if we choose 2,3 and 4, we can get 2^3^4=5. Now, you are given N numbers, and you can choose some of them(even a single number) to do XOR on them, and you can get many different numbers. Now I want you tell me which number is the K-th smallest number among them.
attack
2018/08/01
3350
HHHOJ NOIP2020模拟赛(叁)2020.02.03 题解
三个必要的切割中的两个始终从一个角落G进行(图中G位于A,实际上也可以是B、C、D),第三次切割必须垂直于前面两个之一(在图中,AE部分垂直于EF部分)。
yzxoi
2022/09/19
4160
HHHOJ NOIP2020模拟赛(叁)2020.02.03 题解
新兴的 CCPC,真的能不负算法选手的众望,超越日渐下滑的 ICPC 吗
近几年,ICPC 的题目质量日渐下滑。CCPC 能不能守住题目质量的最后红线,还给算法竞赛选手一个公平公开的比赛环境。
ACM算法日常
2021/09/28
9550
洛谷P2664 树上游戏(点分治)
考虑点分治,那么每次我们只需要统计以当前点为\(LCA\)的点对之间的贡献以及\(LCA\)到所有点的贡献。
attack
2019/04/09
5450
Day2下午解题报告
预计分数:100+100+30=230 实际分数:100+100+30=230 人品爆发&&智商爆发&&手感爆发 T3数据好水,,要是把数组开大一点的话还能多得10分,,, T1洗澡 原题,不多说了。。 当时在北京花了接近一个小时才A.. 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 using namespace std; 6 const int MAXN=1e6; 7 cons
attack
2018/04/11
5760
「2017 Multi-University Training Contest 1」2017多校训练1
1001 Add More Zero(签到题) 题目链接 HDU6033 Add More Zero image.png #include <cstdio> #include <cmath> int cas,m; int main(){ while(~scanf("%d",&m)){ printf("Case #%d: %d\n",++cas,(int)(m*log(2)/log(10))); } return 0; } 1002 Balala Power!(贪心) 题目链接 HDU6034 Ba
饶文津
2020/06/02
3790
「2017 Multi-University Training Contest 1」2017多校训练1
ZR国庆Round2解题报告
然后刚T3暴力,刚完还有2h左右。。然后,,这时候我zz的选择去打T2的暴力,然而T2暴力真的不是一般的难写。。
attack
2018/10/08
3120
洛谷P3384 【模板】树链剖分
题目描述 如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作: 操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z 操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和 操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z 操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和 输入输出格式 输入格式: 第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号
attack
2018/04/11
6890
洛谷P3384 【模板】树链剖分
“玲珑杯”ACM比赛 Round #13 题解&源码
A 题目链接:http://www.ifrog.cc/acm/problem/1111 分析:容易发现本题就是排序不等式, 将A数组与B数组分别排序之后, 答案即N∑i=1Ai×Bi 此题有坑,反正据
Angel_Kitty
2018/04/08
6790
“玲珑杯”ACM比赛 Round #13 题解&源码
Codeforces Round #360 div2
  有d天, n个人。如果这n个人同时出现, 那么你就赢不了他们所有的人, 除此之外, 你可以赢他们所有到场的人。
熙世
2019/07/14
3730
洛谷P3313 [SDOI2014]旅行(树链剖分 动态开节点线段树)
题意 题目链接 Sol 树链剖分板子 + 动态开节点线段树板子 #include<bits/stdc++.h> #define Pair pair<int, int> #define MP(x, y) make_pair(x, y) #define fi first #define se second //#define int long long #define LL long long #define Fin(x) {freopen(#x".in","r",stdin);} #define Fou
attack
2019/03/01
5300
令人闻风丧胆的NEERC——被称为ICPC题目质量之最的比赛,究竟是怎样的难度
NEERC,ACM-ICPC Northeastern European Regional Contest,是欧洲地区的异常区域赛,因其题目质量高而闻名于ICPC。
ACM算法日常
2021/09/28
1.8K0
Codeforces Round #361 div2
  有n个城市, 第i个城市到第j个城市需要消耗abs(i - j)的能量, 然后每个城市可以转移到另一个城市, 只需要一个能量, 转移是单向的。
熙世
2019/07/14
4720
【2016多校训练4】Multi-University Training Contest 4
树状数组维护数字i前面有多少个比它小的数,即第几小。最左距离就是rank,最右距离就是max(原位置,终位置),求出距离极差即可。
饶文津
2020/06/02
2490
Codeforces Round #313 (Div. 2)
大半年没有打Codeforces , 昨天开始恢复打Codeforces, 简直是, 欲语泪先流啊。
熙世
2019/07/15
3990
Codeforces Round #313 (Div. 2)
loj#2542. 「PKUWC2018」随机游走(MinMax容斥 期望dp)
设\(f[i][sta]\)表示从\(i\)到集合\(sta\)中任意一点的最小期望步数
attack
2019/01/03
4970
推荐阅读
相关推荐
51Nod-1232-完美数
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档