[Java-多线程] 锁原理(轻量级锁、锁膨胀、自旋锁、偏向锁)

1.Java对象头

我们平时使用的对象都是由两部分组成, 第一部分是对象头, 第二部分是对象的成员变量, 这里我么主要讲解对象头, 以32为虚拟机为例 :

Object Header (64 bits)

Mark Word (32 bits)

Klass Word (32 bits)

Klass Word : 每个对象都有类型 通过Klass Word就可以找到对应的类对象

Mark Word :

Mark Word (64 bits)

State

unused:25 | hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01

Normal (普通的)

thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01

Biased

ptr_to_lock_record:62 | 00

Lightweight Locked (轻量级锁定)

ptr_to_heavyweight_monitor:62 | 10

Heavyweight Locked (重量级锁定)

| 11

Marked for GC

数字 01 代表的是加锁状态位


2.轻量级锁

使用场景: 如果一个对象虽然有很多线程正在访问, 但是多线程访问的时间都是错开的, 也就是没有竞争, 那么可以使用轻量级锁来优化

注 : 轻量级锁对于开发者来说是透明的, 仍然使用synchronized关键字

如下代码所示, 两个同步代码块的方法, 利用同一个对象锁

static final object obj new object();
public static void method1(){
synchronized(obj ){
    //同步块A 
    method2();
    }
}
public static void method2(){
synchronized(obj ){
    //同步块B
    }
}

2.1加锁

如果线程 Thread-0 直行到代码 synchronized(obj ), 就会在该方法的栈针中创建锁记录(Lock Record)对象, 这个锁记录对象其中包含两部分, 第一部分是对象指针, 另一部分存储我们要加锁对象的Mark Word 地址

  1. 让Object reference(指向的是对象的地址信息,方便通过ObjectMonitor来访问对应的锁对象) 指向右边的对象

  1. 将锁记录的数据和对象的Mark Word做交换, 表示给对象加锁, 这个带大家回顾一下对象头格式

在正常状态下Normal 最后两位显示的是01 但是如果是轻量级锁, 最后两位显示的是00, 最后交换的数据如下图所示

2.2解锁

当退出synchronized代码块(解锁时)锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头, 也就是将数据再次的交换


3.锁膨胀

场景 : 当其他线程Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁, Thread-1加锁失败进入膨胀锁流程, 改为重量级锁

这时Mark Word会指向 monitor 重量级锁的地址, 并且后两位会变成10

当Thread-0退出同步块解锁时,使用cas将Mark Word的值恢复给对象头,失败。这时会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中BLOCKED线程

了解Monitor如何解锁可以看这篇文章 : https://blog.csdn.net/qq_45481709/article/details/128651642?spm=1001.2014.3001.5501


4.自旋锁

重量级锁竞争的时候, 还可以使用自旋锁来优化, 如果当前线程自旋成功, 就避免了阻塞也就是减少了线程的上下文切换, 使得系统的性能开销减少

当线程2自旋重试的过程中, 线程1已经成功解锁, 在发现已经是无锁之后,线程2成功加锁就不会陷入阻塞状态

自旋锁失败情况

自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会
高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
Java 7 之后不能控制是否开启自旋功能

5.偏向锁

轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。

Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现

这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有

例如:

static final Object obj = new Object();
public static void m1() {
synchronized( obj ) {
// 同步块 A
m2();
 }
}
public static void m2() {
synchronized( obj ) {
// 同步块 B
m3();
 }
}
public static void m3() {
synchronized( obj ) {
// 同步块 C
 }
}

在改为偏向锁之后, 就会将将线程 ID 设置到对象的 Mark Word 头

扫描下方公众号二维码 回复: 多线程 领取多线程面试题 👇 👇 👇

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Max恒

为了开源加油 ! !

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值