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 地址
让Object reference(指向的是对象的地址信息,方便通过ObjectMonitor来访问对应的锁对象) 指向右边的对象
将锁记录的数据和对象的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 头
扫描下方公众号二维码 回复: 多线程 领取多线程面试题 👇 👇 👇