书接上回
当我们对一个代码块或者对象加synchronize关键字时,在早期的jdk版本中时直接申请系统锁,然后将内存锁住,这样的结果就是大大降低执行效率。在JDK1.6版本后对synchronized的实现进行了各种优化,自旋锁、偏向锁和轻量级锁
锁的4中状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高)
偏向锁
偏向第一个拿到锁的线程。
即第一个拿到锁的线程,锁会在对象头 Mark Word 中通过 CAS 记录该线程 ID,该线程以后每次拿锁时都不需要进行 CAS(指轻量级锁)。
如果该线程正在执行同步代码块时有其他线程在竞争(指其他线程尝试 CAS 让 Mark Word 设置自己的线程 ID),会被升级为轻量级锁。
如果其他线程发现 Mark Word 里记的不是自己,且发现原持有偏向锁的线程已经执行完同步代码块,会尝试 CAS 把 Mark Word 中的改为自己的线程 ID。
轻量级锁
轻量级锁就是通过 CAS 进行加锁的。
JVM 会给线程的栈帧中创建一个叫锁记录 Lock Record 的空间,把对象头 Mark Word 复制到该空间里(Displaced Mark Word),并通过 CAS 尝试把原对象头 Mark Word 中锁记录指针指向该锁记录。如果成功,表示线程拿到了锁。如果失败,则进行自旋(自旋锁),自旋超过一定次数时升级为重量级锁,这时该线程会被内核挂起。
自旋锁
轻量级锁膨胀为重量级锁前,线程在执行 monitorenter 指令进入等待队列时,会通过自旋去尝试获得锁。
如果自旋超过一定次数时还未拿到锁,就会进入阻塞状态,等待内核来调度。此时会发生内核态与用户态之间的上下文切换,所以会影响性能(引入自旋锁就是为了减少这个开销)。
因为后面的线程也先进行自旋尝试获取锁,所以这对于已被阻塞的那些线程来说,会不公平。
重量级锁
重量级锁就是通过内核来操作线程。因为频繁出现内核态与用户态的切换,会严重影响性能。
锁升级验证过程
在没有加锁的情况下,锁对象mark word 分别记录着对象的hashCode,堆中的分代年龄,以及锁标志位。
public class SynchronizedDemo2 {
public static Unsafe unsafe;
public static SynchronizedDemo2 synchronizedDemo = new SynchronizedDemo2();
public static void main(String[] args) throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
System.out.println("对象HASH:" + getLongBinaryString(synchronizedDemo.hashCode()));
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
}
private static String getLongBinaryString(long num) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 64; i++) {
if ((num & 1) == 1) {
sb.append(1);
} else {
sb.append(0);
}
num = num >> 1;
}
return sb.reverse().toString();
}
}
执行结果:
一:新new出来的对象锁状态001;默认无锁。
二:当调用对象HashCode后 MarkWord将记录当前的值
MarkWord:0000000000000000000000000000000000000000000000000000000000000001
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) 05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
12 4 (loss due to the next object alignment)
对象HASH:00000000000000000000000000000000 01011001 00110110 00110100 10101101
MarkWord:0000000000000000000000000101100100110110001101001010110100000001
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 ad 34 36 (00000001 10101101 00110100 00110110) (909421825)
4 4 (object header) 59 00 00 00 (01011001 00000000 00000000 00000000) (89)
8 4 (object header) 05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
12 4 (loss due to the next object alignment)
要验证偏向锁要开启偏向锁立即生效 -XX:BiasedLockingStartupDelay=0
public static void main(String[] args) throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
// System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
//调用hash会使偏向锁转换为无锁,所以这里关闭 hash代码
// System.out.println("对象HASH:" + getLongBinaryString(synchronizedDemo.hashCode()));
// System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
// System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
synchronized (synchronizedDemo){
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
}
synchronized (synchronizedDemo){
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
}
}
运行结果:
当同一个线程对对象加锁,MarkWord会记录当前线程的线程指针,如果是同一个线程进行调用,则进入偏向模式。
MarkWord:0000000000000000000000000000000000000000000000000000000000000101
MarkWord:0000000000000000000000000000000000000010100001110010100000000101
MarkWord:0000000000000000000000000000000000000010100001110010100000000101
public static void main(String[] args) throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
// System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
//调用hash会使偏向锁转换为无锁,所以这里关闭 hash代码
// System.out.println("对象HASH:" + getLongBinaryString(synchronizedDemo.hashCode()));
// System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
// System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
//同一个线程对其加锁
synchronized (synchronizedDemo){
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
}
synchronized (synchronizedDemo){
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
}
//当另外一个线程对其加锁的时候进入轻量级锁状态
Thread thread1 = new Thread(() -> {
synchronized (synchronizedDemo){
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
}
});
thread1.start();
thread1.join();
synchronized (synchronizedDemo){
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
}
}
运行结果:
当有其他线程开始争夺锁资源时,升级为轻量级锁或自旋锁,并且markword记录当前线程的LockRecord指针。
MarkWord:0000000000000000000000000000000000000000000000000000000000000101
MarkWord:0000000000000000000000000000000000000011001010000010100000000101
MarkWord:0000000000000000000000000000000000000011001010000010100000000101
MarkWord:0000000000000000000000000000000000011010111010001111001100001000
com.company.SynchronizedDemo2 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 f3 e8 1a (00001000 11110011 11101000 00011010) (451474184)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
MarkWord:0000000000000000000000000000000000000011000101001111011101001000
com.company.SynchronizedDemo2 object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 48 f7 14 03 (01001000 11110111 00010100 00000011) (51705672)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c1 00 20 (00000101 11000001 00000000 00100000) (536920325)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
public static void main(String[] args) throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
// System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
//调用hash会使偏向锁转换为无锁,所以这里关闭 hash代码
// System.out.println("对象HASH:" + getLongBinaryString(synchronizedDemo.hashCode()));
// System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
// System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
//同一个线程对其加锁
synchronized (synchronizedDemo){
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
}
synchronized (synchronizedDemo){
System.out.println("MarkWord:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
}
//当另外一个线程对其加锁的时候进入轻量级锁状态
Thread thread1 = new Thread(() -> {
synchronized (synchronizedDemo){
System.out.println("MarkWord1:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MarkWord2:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
}
});
thread1.start();
Thread.sleep(100);
synchronized (synchronizedDemo){
System.out.println("MarkWord3:" + getLongBinaryString(unsafe.getLong(synchronizedDemo, 0L)));
System.out.println(ClassLayout.parseInstance(synchronizedDemo).toPrintable());
}
}
执行结果:
注意代码中Thread1 是先启动的 并且等待了1000毫秒,主线程等待100毫秒是开始抢锁。
所以当Thread1持有锁的状态轻量锁,但是100毫秒后主线程开始抢锁,于是此时在争抢到达一定节点,锁升级为重量级锁。主线程必须在thread1执行完后才能拥有锁。
MarkWord1:0000000000000000000000000000000000011011000111111111001110100000
MarkWord2:0000000000000000000000000000000000000011011001011010010000101010
MarkWord3:0000000000000000000000000000000000000011011001011010010000101010
网上有一个很完善的图,这里贴出来。