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虚拟机》第二版,第二十章的内容。

目录
相关文章
|
7天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
15 0
|
9天前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
22 8
|
7天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
7天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
11天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
40 5
|
9天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
9天前
|
安全 Java 程序员
Java内存模型的深入理解与实践
本文旨在深入探讨Java内存模型(JMM)的核心概念,包括原子性、可见性和有序性,并通过实例代码分析这些特性在实际编程中的应用。我们将从理论到实践,逐步揭示JMM在多线程编程中的重要性和复杂性,帮助读者构建更加健壮的并发程序。
|
14天前
|
算法 Java 开发者
Java内存管理与垃圾回收机制深度剖析####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,特别是其垃圾回收机制的工作原理、算法及实践优化策略。不同于传统的摘要概述,本文将以一个虚拟的“城市环卫系统”为比喻,生动形象地揭示Java内存管理的奥秘,旨在帮助开发者更好地理解并调优Java应用的性能。 ####
|
6天前
|
存储 监控 算法
Java内存管理的艺术:深入理解垃圾回收机制####
本文将引领读者探索Java虚拟机(JVM)中垃圾回收的奥秘,解析其背后的算法原理,通过实例揭示调优策略,旨在提升Java开发者对内存管理能力的认知,优化应用程序性能。 ####
20 0
|
18天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
154 1

热门文章

最新文章