不同线程 threadId 一致,导致偏向锁的重新偏向

  • 一、出现重复偏向的代码块
  • 二、多次运行后出现问题的打印结果


一、出现重复偏向的代码块

  • 首先修改 jvm启动参数设置延迟偏向时间 0 [-XX:BiasedLockingStartupDelay=0]
  • 可以看到下面的代码创建了三个异步线程,每个线程的任务是打印线程信息及对象的头信息,线程创建之后立即执行并通过join()方法主线程阻塞,直至异步线程执行结束后再创建新线程执行。
  • 由于偏向锁延迟时间是0,所以第一次打印对象头信息为偏向锁,并且为可偏向状态;第二次为线程1开始执行,再次打印对象头那么头信息也肯定为偏向锁,并且线程id指向线程1;那么线程2开始执行由于偏向锁线程id已经指向线程1,线程2经过CAS操作,获得到的锁必然是轻量级锁,线程3同理也是轻量级锁。
//修改jvm启动参数设置延迟偏向时间 0  [-XX:BiasedLockingStartupDelay=0]

import static java.lang.System.out;

 public static void main(String[] args) throws InterruptedException {
 	A a = new A();
 	out.println(ClassLayout.parseInstance(a).toPrintable());
 	
 	Thread thread1 = new Thread(runnable(a),"thread1");
 	thread1.start();
    thread1.join();
    
    Thread thread2 = new Thread(runnable(a),"thread2");
 	thread2.start();
    thread2.join();
    
    Thread thread3 = new Thread(runnable(a),"thread3");
 	thread1.start();

 }

 static Runnable runnable(A a) {
	  return () -> {
	  	 synchronized (a) {
	  	 	out.println(Thread.currentThread().getId() + ":" + Thread.currentThread().getName());
	  	 	out.println(ClassLayout.parseInstance(a).toPrintable());
	  	 }
 }

二、多次运行后出现问题的打印结果

  • 我们运行了3次,结果都和上述结论状态变化一致:偏向、偏向、轻量、轻量,当我们第四次运行时出现了:偏向、偏向、偏向、偏向的打印结果。
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" 
com.usual.header.A object internals:
OFFSET  SIZE   	    TYPE 				DESCRIPTION
	0     4        (object header)      05 00 00 00 (00000101 00000000 00000000 00000000)(5)   

12:thread1
com.usual.header.A object internals:  
OFFSET  SIZE   		TYPE 				DESCRIPTION
	0     4        (object header)      05 f0 c7 1f (00000101 11110000 11000111 00011111)(533196805)

13:thread2
com.usual.header.A object internals:  
OFFSET  SIZE   		TYPE 				DESCRIPTION
	0     4        (object header)      05 f0 c7 1f (00000101 11110000 11000111 00011111)(533196805)

14:thread3	
com.usual.header.A object internals:  
OFFSET  SIZE   		TYPE 				DESCRIPTION
	0     4        (object header)      05 f0 c7 1f (00000101 11110000 11000111 00011111)(533196805)
  • 根据上述对象头 mark_word 部分打印可知,线程123获得的thread_id是一致的,线程2在线程1运行结束后,由于线程id一致所以无需CAS操作,对象头信息未改变,直接获得偏向锁。
  • 分析可知是java线程id复用导致,因为上一个线程 dead,新的线程可以复用上一个线程的thread_id , 我们可以在thread2线程创建前,新建一个线程并启动,就会发先最终打印结果为偏向、偏向、轻量、轻量,不再出现重新偏向的现象了。