JAVA内存模型之synchronized的实现原理

简介:

synchronized与monitor(监视器)的关系

synchronized 译同步的,但我们平时也称之为锁。它呈现给编程人员的视角是: 
     ① synchronized 作用于普通方法,锁的对象是当前实例
     ② synchronized 作用于静态方法,锁的对象是类的Class对象
     ③ synchronized 作用于方法块,锁的对象是括号里匹配的对象
     如下代码:
public class Test {
    private static int a = 0;
    
    public static void main(String args []) {
        Test test = new Test();
        test.testOne(2);
        
    }
    
    public void testOne(int a) {
        synchronized (this) {
            a++;            
        }
    }
    
    public synchronized void testTwo() {
        a++;
    }
    
    public static synchronized void testThree() {
        a++;
    }
}

反编译后:

  public void testOne(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=2
         0: aload_0
         1: dup
         2: astore_2
         3: monitorenter
         4: iinc          1, 1
         7: aload_2
         8: monitorexit
         9: goto          17
        12: astore_3
        13: aload_2
        14: monitorexit
        15: aload_3
        16: athrow
        17: return
      Exception table:
         from    to  target type
             4     9    12   any
            12    15    12   any
      LineNumberTable:
        line 11: 0
        line 12: 4
        line 13: 7
        line 14: 17
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ class Test, int, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

  public synchronized void testTwo();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #5                  // Field a:I
         3: iconst_1
         4: iadd
         5: putstatic     #5                  // Field a:I
         8: return
      LineNumberTable:
        line 17: 0
        line 18: 8

  public static synchronized void testThree();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #5                  // Field a:I
         3: iconst_1
         4: iadd
         5: putstatic     #5                  // Field a:I
         8: return
      LineNumberTable:
        line 21: 0
        line 22: 8

    可以很清楚的看到,不管synchronized 作用于普通方法还是静态方法,都是在其方法对应的 flags 设置为:ACC_SYNCHRONIZED. 也就是将运行时常量池对应的 method_info 结构中的访问标志置为:ACC_SYNCHRONIZED.
而对于synchronized 作用于方法块,则是使用monitorenter 和 monitorexit 指令来实现。
    不管是将方法的access_flags: 置为ACC_SYNCHRONIZED ,还是采用monitorenter 和 monitorexit 指令。两者的实现都是在进入同步代码的开始处尝试获取对象所关联的监视器(即获取这个对象的锁)
监视器是JVM用来实现synchronized语义的。
Java中的监视器支持两种线程通信:互斥和协作。互斥就是获取实例对象或类对象的锁;协作就是Object 类中的 wait, notify, notifyall 

screenshot


如图:JVM中的监视器模型可以分为三个区域,入口区、持有者、等待区。当一个线程到达监视区的开始处时,它会通过最左边的一号门进入监视器;如果发现没有其它线程持有监视器,也没有其它线程在入口处等待,这个线程就会通过下一道门——2号门,并持有监视器,作为监视器的持有者,它将继续执行监视区域中的代码。也可能出现这样的情况,已经有另一个线程正持有监视器,这个线程会被阻塞。当监视器的持有者执行Object.wait 方法时,会释放对象的锁,进入等待区,直到某个时候持有该对象锁的另一个线程执行了notify方法或notifyAll方法后才从等待区中苏醒,并和入口区和等待区中的其他线程竞争获取对象锁。区别notity 和 notifyAll 方法,notify方法会随机从等待区中唤醒一个线程,notifyAll方法会唤醒全部等待区中的线程。

对象头

Java对象头:以JDK 1.8 为例

// Bit-format of an object header (most significant first, big endian layout below):

//
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
//
// unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
// unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)

锁的标志位:
// [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread
// [0 | epoch | age | 1 | 01] lock is anonymously biased
//
// - the two lock bits are used to describe three states: locked/unlocked and monitor.
//
// [ptr | 00] locked ptr points to real header on stack
// [header | 0 | 01] unlocked regular object header
// [ptr | 10] monitor inflated lock (header is wapped out)
// [ptr | 11] marked used by markSweep to mark an object
// not valid at any other time

偏向锁

特征:对象偏向于某个线程,等到存在锁竞争的时候,才会撤销锁。
缺点:如果线程间存在锁的竞争,会带来额外的锁的撤销操作。
使用场景:使用于只有一个线程访问同步块场景

偏向锁的获取

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下MarkWord中偏向锁的标志是否置为1,如果没有设置则使用CAS竞争锁;如果设置了,尝试使用CAS将对象头的偏向锁指向当前线程。

偏向锁的撤销

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放。

轻量级锁

特征:当锁存在竞争的时候,使用自旋的方式尝试获取锁。
优点:竞争的线程 不会阻塞,提高了程序的响应速度。
缺点:自旋会消耗CPU
适用场景:追求响应时间,同步块执行速度非常快

重量级锁 也叫监视器锁

特征:线程会阻塞
优点:线程竞争不会使用自旋,不会消耗CPU
缺点:线程阻塞,响应时间缓慢
适用场景:追求吞吐量,同步块执行速度较长。


本文是对《Java并发编程的艺术》2.2 synchronized 的实现原理与应用的总结,部分材料引用了《深入Java虚拟机》第二版,第二十章的内容。

目录
相关文章
|
3月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
373 3
|
4月前
|
存储 缓存 Java
Java数组全解析:一维、多维与内存模型
本文深入解析Java数组的内存布局与操作技巧,涵盖一维及多维数组的声明、初始化、内存模型,以及数组常见陷阱和性能优化。通过图文结合的方式帮助开发者彻底理解数组本质,并提供Arrays工具类的实用方法与面试高频问题解析,助你掌握数组核心知识,避免常见错误。
|
2月前
|
Java 大数据 Go
从混沌到秩序:Java共享内存模型如何通过显式约束驯服并发?
并发编程旨在混乱中建立秩序。本文对比Java共享内存模型与Golang消息传递模型,剖析显式同步与隐式因果的哲学差异,揭示happens-before等机制如何保障内存可见性与数据一致性,展现两大范式的深层分野。(238字)
95 4
|
2月前
|
安全 Java 数据库连接
一把锁的两种承诺:synchronized如何同时保证互斥与内存可见性?
临界区指多线程中访问共享资源的代码段,需通过互斥机制防止数据不一致与竞态条件。Java用`synchronized`实现同步,保证同一时刻仅一个线程执行临界区代码,并借助happens-before规则确保内存可见性与操作顺序,从而保障线程安全。
168 11
|
2月前
|
存储 缓存 Java
【深入浅出】揭秘Java内存模型(JMM):并发编程的基石
本文深入解析Java内存模型(JMM),揭示synchronized与volatile的底层原理,剖析主内存与工作内存、可见性、有序性等核心概念,助你理解并发编程三大难题及Happens-Before、内存屏障等解决方案,掌握多线程编程基石。
|
3月前
|
缓存 监控 Kubernetes
Java虚拟机内存溢出(Java Heap Space)问题处理方案
综上所述, 解决Java Heap Space溢出需从多角度综合施策; 包括但不限于配置调整、代码审查与优化以及系统设计层面改进; 同样也不能忽视运行期监控与预警设置之重要性; 及早发现潜在风险点并采取相应补救手段至关重要.
577 17
|
4月前
|
监控 Kubernetes Java
最新技术栈驱动的 Java 绿色计算与性能优化实操指南涵盖内存优化与能效提升实战技巧
本文介绍了基于Java 24+技术栈的绿色计算与性能优化实操指南。主要内容包括:1)JVM调优,如分代ZGC配置和结构化并发优化;2)代码级优化,包括向量API加速数据处理和零拷贝I/O;3)容器化环境优化,如K8s资源匹配和节能模式配置;4)监控分析工具使用。通过实践表明,这些优化能显著提升性能(响应时间降低40-60%)同时降低资源消耗(内存减少30-50%,CPU降低20-40%)和能耗(服务器功耗减少15-35%)。建议采用渐进式优化策略。
236 1
|
4月前
|
存储 监控 算法
Java垃圾回收机制(GC)与内存模型
本文主要讲述JVM的内存模型和基本调优机制。
|
5月前
|
SQL 缓存 安全
深度理解 Java 内存模型:从并发基石到实践应用
本文深入解析 Java 内存模型(JMM),涵盖其在并发编程中的核心作用与实践应用。内容包括 JMM 解决的可见性、原子性和有序性问题,线程与内存的交互机制,volatile、synchronized 和 happens-before 等关键机制的使用,以及在单例模式、线程通信等场景中的实战案例。同时,还介绍了常见并发 Bug 的排查与解决方案,帮助开发者写出高效、线程安全的 Java 程序。
278 0
|
4月前
|
边缘计算 算法 Java
Java 绿色计算与性能优化:从内存管理到能耗降低的全方位优化策略与实践技巧
本文探讨了Java绿色计算与性能优化的技术方案和应用实例。文章从JVM调优(包括垃圾回收器选择、内存管理和并发优化)、代码优化(数据结构选择、对象创建和I/O操作优化)等方面提出优化策略,并结合电商平台、社交平台和智能工厂的实际案例,展示了通过Java新特性提升性能、降低能耗的显著效果。最终指出,综合运用这些优化方法不仅能提高系统性能,还能实现绿色计算目标,为企业节省成本并符合环保要求。
182 0