锁膨胀支持之前,synchronized 在释放和获取锁时都会从用户态转换成核心态,状态转换需要耗费CPU很多时间,所以synchronized是一个重量级操作。
但多数情况下,我们的同步代码块不存在多么激烈的锁竞争,synch显得十分浪费,所以自JDK6对synchronized进行了优化,有了锁膨胀机制。
偏向锁:当某个线程获取锁时,先认为不存在锁竞争,在对象头中设置标记,标记当前对象已被该线程获取,若无其他线程参与的情况,则JVM不需要进行任何其他同步操作,若有其他线程尝试获取锁,则升级为轻量锁。
若程序中的锁总是被不同线程访问,那么偏向锁就是多余的。可能在某种情况下禁用偏向锁反而更提升性能。
轻量锁:进入同步代码块后,JVM会在当前线程栈帧中建立一个称为锁记录(Lock Record)的空间记录锁对象目前对象头MarkWord的信息,然后CAS操作将MarkWord更新为指向该LockRecord的指针。如果更新成功,那么标识当前线程持有了锁,如果更新失败且当前MarkWord指向的不是自己,那么表示锁在被两个以上的线程争抢,这时才膨胀为重量级锁。
下边是对象头MarkWord中的信息,写段程序来验证锁膨胀
图来自https://www.csdn.net/tags/NtzaAg1sNjI5MzktYmxvZwO0O0OO0O0O.html
引入jar
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
public class Test {
public static void main(String[] args) throws InterruptedException, IOException {
TimeUnit.SECONDS.sleep(5L);
Object o = new Object();
System.out.println("偏向锁,无偏向线程");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
System.out.println("偏向锁,偏向当前线程");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
new Thread(new Runnable() {
@Override
public void run() {
synchronized (o) {
System.out.println("新线程获取锁,锁升级为轻量锁");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}).start();
// 等待上边线程执行完毕
TimeUnit.SECONDS.sleep(5);
System.out.println("轻量锁1执行完毕");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
System.out.println("轻量锁2");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
System.out.println("多线程争夺锁,锁升级为重量锁");
new Thread(new Runnable() {
@Override
public void run() {
synchronized (o) {
System.out.println(ClassLayout.parseInstance(o).toPrintable());
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {}
}
}
}).start();
synchronized (o) {
TimeUnit.SECONDS.sleep(1);
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
System.out.println();
}
}
执行结果:(字节序为小端序)
新对象在执行同步块前无偏向线程,执行同步块时锁标识101,后边的则是指向当前线程
当有其他线程尝试获取锁,锁升级为轻量,锁标识00,后边的数则指向栈帧
同步执行完毕,对象头变为无锁状态001,指针清空
升级为重量级锁 ,锁标识为10,指针指向同一个monitor
全部:
偏向锁,无偏向线程
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
偏向锁,偏向当前线程
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 48 1e 03 (00000101 01001000 00011110 00000011) (52316165)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
新线程获取锁,锁升级为轻量锁
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 28 f3 41 21 (00101000 11110011 01000001 00100001) (557970216)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
轻量锁1执行完毕
java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
轻量锁2
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 68 f3 14 03 (01101000 11110011 00010100 00000011) (51704680)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
多线程争夺锁,锁升级为重量锁
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 9a 7d dc 1c (10011010 01111101 11011100 00011100) (484212122)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 9a 7d dc 1c (10011010 01111101 11011100 00011100) (484212122)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total