synchronized偏向锁

147 阅读7分钟

0 前言

之前在学习synchronized时,对偏向锁进行测试的时候遇到一个奇怪的问题,在此记录一下。

1 偏向锁介绍

什么是偏向锁?为什么要有偏向锁?

  • synchronized在1.6 之前只有重量级锁,重量级锁会涉及到线程的挂起和唤醒,底层借助操作系统的互斥锁来实现,有较大的开销,所以1.6之前的synchronized性能是较差的,所以在1.6之后对synchronized进行了优化

  • 偏向锁是Java 6之后加入的新锁,它是一种针对加锁操作的优化手段,经过研究发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁的代价而引入偏向锁。

  • 偏向锁可以简单的从字面意思来理解,意为偏向于某一个线程,如果还是同一个线程来获取资源,则不需要进行上锁,如果是不同的线程来获取资源,则不行。

偏向锁是怎么实现的?

  • 加锁:通过 CAS 操作,将当前线程的地址(也当做唯一ID)记录到 markword 中
  • 偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的
  • 其他线程竞争偏向锁时会尝试通过CAS修改Mark Word中的线程ID,如果成功则重偏向,如果失败就会升级为轻量级锁

2 问题描述

通过源码分析可以得出一个结论,如果偏向锁一来是是线程1获得,此时线程2想要获得偏向锁时发现两个线程不一样,代表有多个线程获取资源,此时应该升级为轻量级锁

但是在本人进行测试时发生了奇怪的现象,测试代码如下:

import org.openjdk.jol.info.ClassLayout;
 
 
public class TestSync3 {
    static Object yesLock = new Object();
    public static void main(String[] args) throws InterruptedException {
        // 这里休眠5秒是因为jvm在4秒之后才会开启偏向锁
        Thread.sleep(5000);
 
        yesLock = new Object();
        System.out.println("无锁时对象布局:" + ClassLayout.parseInstance(yesLock).toPrintable());
        getLock("偏向锁");
        Thread.sleep(3000);
        getLock("轻量级锁");
 
    }
 
    private static void getLock(final String expectLockLevel) {
        new Thread(() -> {
            try {
                synchronized (yesLock) {
                    System.out.println("线程[" + Thread.currentThread().getName() + "]" +
                            ":" + expectLockLevel + "状态对象布局:" + ClassLayout.parseInstance(yesLock).toPrintable());
                    Thread.sleep(2000);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
 
}

正常测试结果如下:

无锁时对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

线程[Thread-0]:偏向锁状态对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020026005 (biased: 0x0000000000080098; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

无锁时对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000020026005 (biased: 0x0000000000080098; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

线程[Thread-1]:轻量级锁状态对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000002072f740 (thin lock: 0x000000002072f740)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

异常测试结果如下:

无锁时对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

线程[Thread-0]:偏向锁状态对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00000000200e5005 (biased: 0x0000000000080394; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

无锁时对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00000000200e5005 (biased: 0x0000000000080394; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

线程[Thread-1]:轻量级锁状态对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x00000000200e5005 (biased: 0x0000000000080394; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
  • 可以看出,明明是前后两个线程获取偏向锁,却有时Thread-1仍然能够获取偏向锁

3 原因分析

关于此问题,困扰了我许久,后来在网上求助某位大佬才得到了答案,原文链接如下:

22 (续01)偏向锁的重入 以及 线程1获取偏向锁并释放线程2获取锁 的调试_970655147的专栏-CSDN博客

  • 偏向锁加锁时,会在Mark Word中写入线程的地址信息
  • 这个线程在java语言层面上是 java.lang.Thread 对象,其实就是比较javaThread对象的内存地址
  • 所以发生这种情况的原因有可能是前后两个Thread对象的内存地址是一样的

为了证明这个说法,可以在代码中输出当前线程的eetop 的信息

Field etopField = Thread.class.getDeclaredField("eetop");
etopField.setAccessible(true);
Object etop = etopField.get(Thread.currentThread());
System.out.println("线程[" + Thread.currentThread().getName() + "]" + "的eetop: " + etop);

结果如下:

无锁时对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

线程[Thread-0]:偏向锁状态对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001fa9f805 (biased: 0x000000000007ea7e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

线程[Thread-0]的eetop: 531232768
无锁时对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001fa9f805 (biased: 0x000000000007ea7e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

线程[Thread-1]:轻量级锁状态对象布局:java.lang.Object object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001fa9f805 (biased: 0x000000000007ea7e; epoch: 0; age: 0)
  8   4        (object header: class)    0xf80001e5
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

线程[Thread-1]的eetop: 531232768
  • 可以看到,确实前后两个Thread对象的地址信息是完全一样的