synchronized 底层如何实现?什么是锁升级、降级?

简介: synchronized 底层如何实现?什么是锁升级、降级?

synchronized 底层如何实现?什么是锁升级、降级?



synchronized 代码块是由一对 monitorenter/monitorexit 指令实现的,Monitor 对象是同步的基本实现单元。

在Java6之前, Monitor的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。现代的( Oracle)JDK中,JVM对此进行了大刀阔斧地改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜锁( Biased Locking)轻量级锁重量级锁,大大改进了其性能。


什么是锁升级,降级?


所谓的锁升级、降级,就是 JVM 优化 synchronized 运行的机制,当 JVM   监测到不同的竞争状况是,会自动切换到不同的锁实现。这种切换就是锁的升级、降级。


对象的结构


说偏向锁之前,需要理解对象的结构,对象由多部分构成的,对象头,属性字段、补齐区域等。所谓补齐区域是指如果对象总大小不是4字节的整数倍,会填充上一段内存地址使之成为整数倍。


640.png



偏向锁又和对象头密切相关,对象头这部分在对象的最前端,包含两部分或者三部分:Mark Words、Klass Words,如果对象是一个数组,那么还可能包含第三部分:数组的长度。

  • Klass Word里面存的是一个地址,占32位或64位,是一个指向当前对象所属于的类的地址,可以通过这个地址获取到它的元数据信息。
  • Mark Word需要重点说一下,这里面主要包含对象的哈希值、年龄分代、hashcode、锁标志位等。

如果应用的对象过多,使用64位的指针将浪费大量内存。64位的JVM比32位的JVM多耗费50%的内存。我们现在使用的64位 JVM会默认使用选项 +UseCompressedOops 开启指针压缩,将指针压缩至32位。

以64位操作系统为例,对象头存储内容图例

|--------------------------------------------------------------------------------------------------------------|
|                                              Object Header (128 bits)                                        |
|--------------------------------------------------------------------------------------------------------------|
|                        Mark Word (64 bits)                                    |      Klass Word (64 bits)    |
|--------------------------------------------------------------------------------------------------------------|
|  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  无锁
|----------------------------------------------------------------------|--------|------------------------------|
|  thread:54 |         epoch:2      | unused:1 | age:4 | biased_lock:1 | lock:2 |     OOP to metadata object   |  偏向锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_lock_record:62                            | lock:2 |     OOP to metadata object   |  轻量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                     ptr_to_heavyweight_monitor:62                    | lock:2 |     OOP to metadata object   |  重量锁
|----------------------------------------------------------------------|--------|------------------------------|
|                                                                      | lock:2 |     OOP to metadata object   |    GC
|--------------------------------------------------------------------------------------------------------------|


对象头中的信息如何理解呢,举个例子


640.png


从该对象头中分析加锁信息,MarkWordk为0x0000700009b96910,二进制为0xb00000000 00000000 01111111 11110000 11001000 00000000 01010011 11101010。倒数第三位为"0",说明不是偏向锁状态,倒数两位为"10",因此,是重量级锁状态,那么前面62位就是指向互斥量的指针。

basied_lock lock 状态
0 01 无锁
1 01 偏向锁
0 00 轻量级锁
0 10 重量级锁
0 11 GC标记
  • age:Java GC标记位对象年龄。
  • identity_hashcode:对象标识Hash码,采用延迟加载技术。当对象使用HashCode()计算后,并会将结果写到该对象头中。当对象被锁定时,该值会移动到线程Monitor中。
  • thread:持有偏向锁的线程ID和其他信息。这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。
  • epoch:偏向时间戳。
  • ptr_to_lock_record:指向栈中锁记录的指针。
  • ptr_to_heavyweight_monitor:指向线程Monitor的指针。

无锁

A a = new A();
  System.out.println(ClassLayout.parseInstance(a).toPrintable());

可以看到最后 00000001 basied_lock = 0, lock =01  表示无锁

JavaThread.synchronizestructure.A 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)                           43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

偏斜锁


当没有竞争出现时,默认使用偏斜锁。JVM 会利用 CAS 操作在对象头上的 Mark Word 部分设置线程 ID ,以表示对象偏向当前线程。所以并不涉及真正的互斥锁,这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竟争开销。测试代码:


Thread.sleep(5000);
A a = new A();
synchronized (a) {
    System.out.println(ClassLayout.parseInstance(a).toPrintable());
}

运行结果:


JavaThread.synchronizestructure.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 28 8d 02 (00000101 00101000 10001101 00000010) (42805253)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           43 c0 00 20 (01000011 11000000 00000000 00100000) (536920131)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

00000101 中 basied_lock = 1, lock =01  表示偏斜锁


轻量级锁


thread1中依旧输出偏向锁,主线程获取对象A时,thread1虽然已经退出同步代码块,但主线程和thread1仍然为锁的交替竞争关系。故此时主线程输出结果为轻量级锁。测试代码:


Thread.sleep(5000);
A a = new A();
Thread thread1= new Thread(){
  @Override
  public void run() {
      synchronized (a){
          System.out.println("thread1 locking");
          System.out.println(ClassLayout.parseInstance(a).toPrintable()); //偏向锁
      }
  }
};
thread1.start();
thread1.join();
Thread.sleep(10000);
synchronized (a){
  System.out.println("main locking");
  System.out.println(ClassLayout.parseInstance(a).toPrintable());//轻量锁
}
}

运行结果:


JavaThread.synchronizestructure.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 40 e9 19 (00000101 01000000 11101001 00011001) (434716677)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c0 00 20 (10100000 11000000 00000000 00100000) (536920224)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
main locking
JavaThread.synchronizestructure.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           38 f6 c7 02 (00111000 11110110 11000111 00000010) (46659128)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           a0 c0 00 20 (10100000 11000000 00000000 00100000) (536920224)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

00000101  依然是偏向锁,00111000 是轻量级锁


重量级锁


thread1 和 thread2 同时竞争对象a,此时输出结果为重量级锁

测试代码:


Thread.sleep(5000);
A a = new A();
Thread thread1 = new Thread(){
    @Override
    public void run() {
        synchronized (a){
            System.out.println("thread1 locking");
            System.out.println(ClassLayout.parseInstance(a).toPrintable());
            try {
                //让线程晚点儿死亡,造成锁的竞争
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
};
Thread thread2 = new Thread(){
    @Override
    public void run() {
        synchronized (a){
            System.out.println("thread2 locking");
            System.out.println(ClassLayout.parseInstance(a).toPrintable());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
};
thread1.start();
thread2.start();

运行结果:


thread2 locking
JavaThread.synchronizestructure.A object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           7a f5 99 17 (01111010 11110101 10011001 00010111) (395965818)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           62 c1 00 20 (01100010 11000001 00000000 00100000) (536920418)
     12     1   boolean A.flag                                    false
     13     3           (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total


01111010  basied_lock = 0 lock=10 重量级锁

相关文章
|
6月前
|
存储 Java 数据安全/隐私保护
【JUC】ThreadLocal 如何实现数据的线程隔离?
【1月更文挑战第15天】【JUC】ThreadLocal 如何实现数据的线程隔离?ThreadLocal 导致内存泄漏问题?
|
Java 编译器
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
解密Java多线程中的锁机制:CAS与Synchronized的工作原理及优化策略
|
3月前
|
存储 Java C++
人人都会的synchronized锁升级,你真正从源码层面理解过吗?
本文结合Jvm源码层面分析synchronized锁升级原理,可以帮助读者从本质上理解锁升级过程,从锁如何存储,存储在哪的疑问出发,一步一步彻底剖析偏向锁,轻量级锁,自旋锁,重量级锁的原理和机制。
人人都会的synchronized锁升级,你真正从源码层面理解过吗?
|
4月前
|
算法 Java 编译器
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
多线程线程安全问题之系统层面的锁优化有哪些常见的策略
|
4月前
|
监控 Sentinel 缓存
高并发架构设计三大利器:缓存、限流和降级问题之RateLimiter的acquire()方法有什么作用
高并发架构设计三大利器:缓存、限流和降级问题之RateLimiter的acquire()方法有什么作用
|
5月前
|
Java Linux
如何实现无锁并发:深入理解CAS原理
如何实现无锁并发:深入理解CAS原理
208 2
|
5月前
|
安全 Java 编译器
synchronized同步锁 : 原理到锁升级及历史演进的解析
synchronized同步锁 : 原理到锁升级及历史演进的解析
|
6月前
|
缓存 NoSQL Java
【亮剑】分布式锁是保证多服务实例同步的关键机制,常用于互斥访问共享资源、控制访问顺序和系统保护,如何使用注解来实现 Redis 分布式锁的功能?
【4月更文挑战第30天】分布式锁是保证多服务实例同步的关键机制,常用于互斥访问共享资源、控制访问顺序和系统保护。基于 Redis 的分布式锁利用 SETNX 或 SET 命令实现,并考虑自动过期、可重入及原子性以确保可靠性。在 Java Spring Boot 中,可通过 `@EnableCaching`、`@Cacheable` 和 `@CacheEvict` 注解轻松实现 Redis 分布式锁功能。
123 0
|
存储 安全 Java
【多线程】线程安全问题原因与解决方案
线程不安全的原因及解决方案,synchronized,volatile
|
11月前
|
安全 Java 编译器
【多线程】线程安全问题和解决方案
【多线程】线程安全问题和解决方案