1 Java对象头
普通对象
|---------------------------------------------------------------------|
| Object Header (64 bits) |
|----------------------------------|----------------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|----------------------------------|----------------------------------|
数组对象
|---------------------------------------------------------------------|----------------------------------|
| Object Header (96 bits) |
|----------------------------------|----------------------------------|----------------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) | array length (32 bits) |
|----------------------------------|----------------------------------|----------------------------------|
其中 Mark word 结构为
|---------------------------------------------------------|--------------------|
| Mark Word (32 bits) | State |
|---------------------------------------------------------|--------------------|
| hashcode: 25 | age: 4 | biased_lock: 0 | 01 | Normal |
|---------------------------------------------------------|--------------------|
| thread: 23 | epoch: 2 | age: 4 | biased_lock: 1 | 01 | Biased |
|---------------------------------------------------------|--------------------|
| ptr_to_lock_record: 30 | 01 | Lightweight Locked |
|---------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor: 30 | 00 | Heavyweight Locked |
|---------------------------------------------------------|--------------------|
| | 11 | Mark for GC |
|---------------------------------------------------------|--------------------|
2 Monitor
2.1 概念
是一个对象,当线程执行synchronized(obj)时,obj对象(锁对象)会将Mark Word被指向一个Monitor对象。
2.2 结构
2.3 流程
- 刚开始 Monitor 中 Owner 为 null
- 当Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor 中只能有一个 Owner。
- 在Thread-2 上锁的过程中,Thread-3、Thread-4也来执行 synchronized(obj) ,就会进入EntryList 中阻塞BLOCKED。
- 在Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争是非公平的。
- WaitSet中的 Thread-0、Thread-1是之前获得过锁,但条件不满足进入Waiting状态的线程。
3 轻量级锁
3.1 概念
当一个对象虽然有多线程访问,但多线程访问的时间错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
3.2 举例
static final Object lock = new Object();
public static void method1() {
synchronized (lock) {
System.out.println("方法1...");
method1();
}
}
public static void method2() {
synchronized (lock) {
System.out.println("方法2...");
}
}
(1)线程-0 栈帧 里产生锁记录Lock record
Lock record 里锁对象的 Markword
Object refence 里存 锁对象地址
(2)让锁记录中的 Object refence指向锁对象,并尝试用cas替换 Object 的 MarkWord,将Mark Word的值存入锁记录。
(3)如果cas成功,对象头中存储了锁记录地址和状态 00 ,表示由该线程给对象加锁
(4)如果cas失败分为两种情况
- 如果是其他线程已经持有了该Object的轻量级锁,这时表明有竞争,进入锁膨胀过程
- 如果是当前线程synchronized锁重入,那么再添加一条 Lock Record作为锁重入的计数
(5)解锁1,如果有取值为null的锁记录;表示有锁重入,这时重置锁记录,表示重入计数减一
(6)解锁2,锁记录不为null;这时cas要将Mark word的值恢复给对象, - 成功 解锁成功
- 失败 表示当前已经升级为重量级锁,进入重量级锁解锁流程
4 锁膨胀
4.1 概念
在尝试cas操作对对象加轻量级锁时发生竞争,那么轻量级锁会膨胀为重量级锁。
4.2 流程
(1)Thread-1 给 Object 加轻量级锁时发现 Thread-0已经对 Object 对象加了轻量级锁,那么进入锁膨胀
(2)为 Object 对象申请Monitor对象,让Object指向重量级锁地址
然后自己进入Monitor的EntryList BLOCKED 阻塞
5 自旋锁
重量级锁竞争的时候,还可以进行自旋进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁)
这时当前线程就可以避免阻塞。
自旋成功的情况
自旋失败的情况
6 偏向锁
6.1 概念
轻量级锁没有竞争时,每次重入仍然需要执行CAS操作
JDK6中引入偏向锁来做进一步优化,只有第一次使用CAS将线程ID设置到MarkWord头,之后发现这个线程ID是自己的表示没有竞争,不需要重新CAS。以后不发生竞争,这个对象就归该线程所有
static final Object lock = new Object();
public static void method1() {
synchronized (lock) {
System.out.println("方法1...");
method2();
}
}
public static void method2() {
synchronized (lock) {
System.out.println("方法2...");
method3();
}
}
public static void method3() {
synchronized (lock) {
System.out.println("方法3...");
}
}
6.2 偏向状态
|---------------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|---------------------------------------------------------|--------------------|
| unused: 25 |hashcode: 25| age: 4 | biased_lock: 0 | 01 | Normal |
|---------------------------------------------------------|--------------------|
| thread: 54 | epoch: 2 | age: 4 | biased_lock: 1 | 01 | Biased |
|---------------------------------------------------------|--------------------|
| ptr_to_lock_record: 62 | 01 | Lightweight Locked |
|---------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor: 62 | 00 | Heavyweight Locked |
|---------------------------------------------------------|--------------------|
| | 11 | Mark for GC |
|---------------------------------------------------------|--------------------|
- 默认情况开启的
- 偏向锁是有延迟的,不会在程序启动时生效,如果想避免延迟,可以加VM参数 XX:BiasedLockingStartupDelay=0来禁用延迟
- 如果没有开启偏向锁,那么对象创建后,markword 的值0x01即为最后三位001,这时hashcode、age都为0;hashcode懒加载
6.3 撤销
多个对象竞争时会将偏向锁撤销
6.4 批量重偏向
如果对象虽然被多个线程访问,但没有发生竞争,这时偏向了T1的对象仍然有机会重新偏向T2,重偏向会重置对象的ThreadID
- 当撤销偏向锁的阈值超过20次后,jvm会认为是不是偏向错了,于是会在给对象加锁时重新偏向至加锁线程
- 当撤销偏向锁的阈值超过40次后,jvm认为确实是偏向错了,根本就不该偏向,于是整个类的所有对象都会变成不可偏向的,新建的对象也变成不可偏向的