当前位置: 首页 > news >正文

synchronized的原理和锁升级

首先,synchronized关键字的基本作用,它是用来实现线程同步的,
保证同一时刻只有一个线程执行某个方法或代码块。

接下来,synchronized在不同应用场景下的实现方式。
比如,当用在实例方法、静态方法或代码块时,底层实现会不会有不同。
这时候需要回忆Java对象头的内容,因为synchronized的实现与对象头中的Mark Word密切相关。

然后,需要解释synchronized的锁升级过程,比如偏向锁、轻量级锁、重量级锁。
用户可能听说过这些概念,但不太清楚具体是怎么转换的,以及为什么要有这样的设计。
这时候需要详细说明每个锁的状态和转换条件,以及它们在不同竞争情况下的表现。

另外,用户可能对synchronized的性能影响感兴趣,比如为什么早期版本的synchronized性能较差,
而后来通过锁优化(如自旋锁、适应性自旋、锁消除、锁粗化等)提升了性能。
这些优化措施的具体实现原理需要简要提及。

还要考虑synchronized与JVM的关系,
比如JVM是如何通过监视器(Monitor)来实现锁的获取和释放的,以及对象头中的锁标志位是如何变化的。
可能需要提到进入和退出同步块时,字节码层面的指令,如monitorenter和monitorexit。

此外,用户可能存在的疑问点包括:synchronized是否公平锁?
它的实现是否依赖操作系统层面的互斥量?
在回答时应该指出,synchronized是非公平锁,且在竞争激烈时会升级为重量级锁,
涉及到操作系统互斥量的使用,导致上下文切换开销。

最后,需要总结synchronized的适用场景,以及在不同JDK版本中的优化,
帮助用户在实际开发中更好地使用synchronized,避免常见的误区,比如过度同步导致的性能问题。

在组织回答时,应该先概述synchronized的作用,然后分点详细讲解其底层实现,
包括对象头、锁升级过程、JVM的优化措施,最后结合实际应用场景给出建议。
这样结构清晰,用户能够逐步深入理解原理。

synchronized 关键字的底层原理详解

目录

一、synchronized 的作用与使用场景

二、底层实现机制

1. Java 对象头(Object Header)和 Monitor(监视器)模型

2. 锁升级过程

3. 锁升级流程图

三、字节码层面的实现

四、JVM 对 synchronized 的优化

4.1、自旋锁与适应性自旋(Spin Lock & Adaptive Spinning)

4.1.1. 自旋锁

4.1.2. 适应性自旋

4.2、锁消除(Lock Elimination)

4.3、锁粗化(Lock Coarsening)

4.4、批量重偏向与撤销(Bulk Rebiasing & Revocation)

4.4.1. 批量重偏向(Bulk Rebiasing)

4.4.2. 批量撤销(Bulk Revocation)

4.5、轻量级锁的快速获取与释放

4.6、锁膨胀策略

4.7、优化对比与适用场景

五、常见问题与解答

1. synchronized 是公平锁吗?

2. 锁升级是否可逆?

3. synchronized 和 ReentrantLock 的区别?

六、总结



一、synchronized 的作用与使用场景

  • 作用:保证多线程环境下,同一时刻只有一个线程能访问被同步的代码或对象。它提供了互斥性、可见性和有序性三个基本特性,确保了线程间操作的原子性和数据的一致性。是 java 中最常用的保证线程安全的方式。

    synchronized 的作用主要有三方面:

    • 互斥性: 当一个线程进入了 synchronized 代码块或方法时,其他试图进入该同步区域的线程必须等待,直至拥有锁的线程执行完毕并释放锁。这确保了在同一时间只能有一个线程访问共享资源,避免了竞态条件和数据不一致的问题。
      • 确保线程互斥的访问代码块,同一时刻只有一个方法可以进入到临界区
    • 可见性: synchronized 关键字还确保了线程间的数据可见性。一旦一个线程在 synchronized 块中修改了共享变量的值,其他随后进入同步区域的线程可以看到这个更改。这是因为解锁过程会将工作内存中的最新值刷新回主内存,而加锁过程则会强制从主内存中重新加载变量的值
      • 保证共享变量的修改能及时可见
    • 有序性: synchronized 提供的第三个特性是有序性,它可以确保在多线程环境下,对于同一个锁的解锁操作总是先行于随后对同一个锁的加锁操作。这就建立了线程之间的内存操作顺序关系,有效地解决了由于编译器和处理器优化可能带来的指令重排序问题
      • 有效解决重排序问题
  • 使用方式:

    • 修饰实例方法(普通方法):锁对象是当前对象实例(this)。这意味着在同一时刻,只能有一个线程访问此方法,其他线程需要等待当前线程执行完毕才能执行该方法。修饰实例方法的方式可以确保对实例变量的访问是线程安全的。

    • public synchronized void methodName() {
          // synchronized 代码块
      }
      
    • 修饰静态方法:锁对象是当前类的 Class 对象(静态方法是属于类,而不是对象)。因此,无论多少个该类的实例存在,同一时刻也只有一个线程能够访问此静态同步方法。这种方式可以确保对静态变量的访问是线程安全的

    • public static synchronized void staticMethodName() {
          // synchronized 代码块
      }
    • 修饰代码块:显式指定锁对象(锁的是括号里的对象,如 synchronized(obj))。需要同步的代码包裹在 synchronized 关键字所修饰的代码块中。通过指定对象作为锁,可以更精确地控制同步范围,只有持有该对象锁的线程才能执行被synchronized修饰的代码块

    • synchronized (obj) {
          // 需要同步的代码块
      }
      
  • synchronized 可以实现的锁

    • 可重入锁(Reentrant Lock): synchronized 实现的锁是可重入的,允许同一个线程多次获取同一个锁,而不会造成死锁。这意味着线程在持有锁的情况下可以再次获取相同的锁,而不会被阻塞。

    • 排它锁/互斥锁/独占锁: synchronized 实现的锁是排它的,也就是说,在任意时刻只有一个线程能够获取到锁,其他线程必须等待该线程释放锁才能继续执行。这确保了同一时刻只有一个线程可以访问被锁定的代码块或方法,从而保证了数据的一致性和完整性

    • 悲观锁: synchronized 实现的锁属于悲观锁,因为它默认情况下假设会发生竞争,并且会导致其他线程阻塞,直到持有锁的线程释放锁。悲观锁的特点是对并发访问持保守态度,认为会有其他线程来竞争共享资源,因此在访问共享资源之前会先获取锁。

    • 非公平锁: 在早期的 Java 版本中,默认实现的 synchronized 是非公平锁,也就是说,线程获取锁的顺序并不一定按照它们请求锁的顺序来进行,而是允许“插队”,即已经在等待队列中的线程可能被后来请求锁的线程抢占。


二、底层实现机制

1. Java 对象头(Object Header)和 Monitor(监视器)模型
  • 对象头结构(以 64 位 JVM 为例):每个对象都有一个对象头

    • Mark Word(64 bits):用于标识对象的锁状态,存储对象的哈希码、锁状态、GC 分代年龄等。

    • Klass Pointer(64 bits):指向类的元数据(Class 对象)。

    • (数组对象额外包含数组长度)

    Mark Word 在不同锁状态下的内容

    锁状态存储内容
    无锁哈希码、分代年龄、偏向模式(0)
    偏向锁线程ID、Epoch、分代年龄、偏向模式(1)
    轻量级锁指向栈中锁记录(Lock Record)的指针
    重量级锁指向 Monitor 对象的指针
  • Monitor(监视器锁)

    • Monitor 是一种同步机制,负责管理对象的锁,每个对象关联一个 Monitor。当一个线程尝试进入一个被synchronized修饰的代码块或方法时,它会尝试获取对象的 Monitor。如果 Monitor 处于无锁状态,则当前线程会尝试将其锁定;如果 Monitor 已经被其他线程锁定,则当前线程会进入阻塞状态,直到持有锁的线程释放锁。由 JVM 实现(如 C++ 的 ObjectMonitor)。

    • 核心字段

      • _owner:持有锁的线程。

      • _WaitSet:等待队列(调用 wait() 的线程)。

      • _EntryList:阻塞队列(竞争锁失败的线程)。

      • _count:重入次数。


2. 锁升级过程

JVM 为优化 synchronized 性能,设计了 锁升级 机制(JDK6+):

底层实现机制如下:

  • 偏向锁(Bias Locking):当一个线程第一次访问一个同步块时,它会尝试获取对象的偏向锁。如果对象没有被其他线程锁定,那么当前线程会尝试将对象的偏向锁设置为自己。之后,当该线程再次访问同步块时,不需要再次获取锁,直接进入同步块执行。这样可以提高同步操作的性能,减少不必要的竞争。
  • 轻量级锁(Lightweight Locking):如果对象已经被其他线程获取了偏向锁,但当前线程又想要获取锁,就会升级为轻量级锁。轻量级锁的获取过程包括尝试将对象头的 Mark Word 设置为指向当前线程的锁记录(Lock Record)和CAS(Compare and Swap)操作。如果锁竞争激烈,会升级为重量级锁
  • 重量级锁(Heavyweight Locking):如果对象的锁被多个线程竞争,那么会升级为重量级锁。重量级锁使用操作系统的互斥量(Mutex)来实现同步,涉及到用户态和内核态的切换,性能相对较低,但能够确保线程的互斥访问
  1. 偏向锁(Biased Locking)

    • 适用升级场景:单线程无竞争环境

      • 其他线程尝试获取锁时,触发偏向锁撤销。

      • 批量撤销(Bulk Revocation):当某一类的偏向锁撤销次数超过阈值(默认 20),JVM 会禁用该类的偏向锁。

    • 实现原理

      • 对象头标记:对象头中的 Mark Word 记录偏向线程 ID 和 Epoch(版本号)。

      • 首次访问:当线程首次进入同步块时,通过 CAS 操作将线程 ID 写入对象头,并进入偏向模式。

      • 重入:同一线程后续进入同步块时,直接检查线程 ID 是否匹配,无需 CAS。后续同一线程可直接进入同步块,无需额外操作。

    • 优势:消除无竞争下的同步开销。

    • 触发条件:默认开启(可通过 -XX:-UseBiasedLocking 禁用)。

    • 优化效果:单线程下完全无锁竞争,性能接近无同步。

  2. 轻量级锁(Lightweight Lock)

    • 适用场景低并发,多线程交替执行

    • 实现原理

      • Lock Record:线程在栈帧中创建锁记录(存储对象头的 Displaced Mark Word)。

      • CAS 替换:通过 CAS 将对象头的 Mark Word 替换为指向 Lock Record 的指针。

      • 成功:线程获得锁,对象头标记为轻量级锁状态。

      • 失败:锁竞争,升级为重量级锁。

    • 优势:避免直接使用操作系统互斥量(Mutex),减少上下文切换。减少多线程交替执行时的锁开销

    • 触发条件偏向锁撤销后,或直接竞争轻量级锁

    • 优化效果:避免操作系统级线程阻塞,通过 CAS 自旋减少上下文切换。

  3. 重量级锁(Heavyweight Lock)

    • 适用场景高并发,多线程竞争激烈

    • 实现原理

      • Monitor 对象:对象头指向操作系统级的互斥量(pthread_mutex_t),未抢到锁的线程进入阻塞队列(_EntryList)。

      • 阻塞队列:竞争失败的线程进入 _EntryList 队列,等待唤醒。依赖操作系统互斥量(如 pthread_mutex_t)实现线程阻塞与唤醒。

    • 劣势:涉及用户态到内核态的切换,开销较大

    • 触发条件轻量级锁自旋失败竞争激烈

JDK 1.6 之前,synchronized 关键字的实现确实被认为是重量级锁。其原理基于操作系统提供的互斥量(Mutexes)来实现线程间的同步,这涉及了从用户态到内核态的切换以及线程上下文切换等相对昂贵的操作。一旦一个线程获得了锁,其他试图获取相同锁的线程将会被阻塞,这种阻塞操作会导致线程状态的改变和 CPU 资源的消耗。

具体而言,在 JDK 1.6 之前,synchronized 的实现过程大致如下:

  • 当线程尝试进入 synchronized 代码块时,它会尝试获取对象的 Monitor(监视器)。

  • 如果 Monitor 的锁状态是无锁状态(Unlocked),则当前线程将尝试获取锁。如果获取成功,则进入临界区执行代码。
  • 如果锁已经被其他线程持有,则当前线程将进入阻塞状态,等待锁被释放。
  • 当持有锁的线程退出临界区并释放锁时,等待的线程将被唤醒,重新尝试获取锁。
  • 这种实现方式存在以下问题:

  • 需要从用户态到内核态的切换,以及线程上下文的切换,导致性能开销较大。
  • 对于竞争不激烈的情况,阻塞等待锁的线程可能会浪费大量的 CPU 时间。
  • 线程在竞争锁的过程中可能会发生多次上下文切换,影响性能。
  • 因此,在高并发、低锁竞争的情况下,早期版本的 synchronized 实现可能成为性能瓶颈。

为了解决这些问题,在 JDK 1.6 中引入了偏向锁(Biased Locking)和轻量级锁(Lightweight Locking)等优化机制,提高了 synchronized 的性能和并发能力。


3. 锁升级流程图
无锁 → 偏向锁(单线程访问)
       ↓
轻量级锁(多线程交替访问,CAS 竞争失败)
       ↓
重量级锁(多线程持续竞争)




[无锁] → (单线程访问) → [偏向锁] 
            ↓               ↖ 撤销
(多线程交替竞争) → [轻量级锁] → (自旋失败/竞争激烈) → [重量级锁]

三、字节码层面的实现

  • 同步代码块:通过 monitorenter 和 monitorexit 指令实现。

    public void syncBlock() {
        synchronized (obj) {
            // 代码
        }
    }

    对应字节码

    0: aload_1              // 加载锁对象 obj
    1: dup                  
    2: astore_2             
    3: monitorenter         // 进入同步块
    4: aload_2             
    5: monitorexit          // 正常退出同步块
    6: goto          14
    9: astore_3             
    10: aload_2             
    11: monitorexit         // 异常退出同步块(确保锁释放)
    12: aload_3             
    13: athrow              
    14: return
  • 同步方法:方法访问标志添加 ACC_SYNCHRONIZED,由 JVM 隐式加锁。

    public synchronized void syncMethod() {
        // 代码
    }

    字节码标志

    flags: ACC_PUBLIC, ACC_SYNCHRONIZED

四、JVM 对 synchronized 的优化

4.1、自旋锁与适应性自旋(Spin Lock & Adaptive Spinning)
4.1.1. 自旋锁
  • 目标减少线程阻塞/唤醒的开销。

  • 原理

    • 线程在竞争轻量级锁失败后,不立即阻塞,而是循环尝试获取锁(自旋)

    • 默认自旋次数:JDK6 前固定 10 次,可通过 -XX:PreBlockSpin 调整。

  • 适用场景:锁持有时间短(如纳秒级),竞争不激烈。

  • 缺点:自旋时间过长会浪费 CPU 资源。

4.1.2. 适应性自旋
  • 目标动态调整自旋次数,平衡 CPU 开销与阻塞成本

  • 原理

    • 根据 历史自旋成功率 动态调整自旋次数。

    • 某锁对象自旋成功率低,后续直接升级为重量级锁

  • 优化效果:在高竞争场景下减少无效自旋。


4.2、锁消除(Lock Elimination)
  • 目标删除不必要的同步操作,锁消除是发生在编译器级别的一种锁优化方式,有时候我们写的代码完全不需要加锁,却执行了加锁操作。

  • 原理

    • 逃逸分析:JIT 编译器通过分析对象作用域,判断对象是否逃逸出当前线程。

    • 锁消除:若对象未逃逸(即线程私有),同步操作会被移除。

  • 示例:StringBuffer类的append操作,从源码中可以看出,append方法用了synchronized关键词,它是线程安全的。但我们可能仅在线程内部把StringBuffer当作局部变量使用:

    public String concat(String s1, String s2) {
        StringBuffer sb = new StringBuffer(); // sb 未逃逸
        sb.append(s1);                        // append 方法同步
        sb.append(s2);
        return sb.toString();
    }
    • JVM 会消除 StringBuffer.append() 的同步操作

  • 触发条件:需开启逃逸分析(默认开启,-XX:+DoEscapeAnalysis)。


4.3、锁粗化(Lock Coarsening)

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,但是大某些情况下,一个程序对同一个锁不间断、高频地请求、同步与释放,会消耗掉一定的系统资源,因为锁的讲求、同步与释放本身会带来性能损耗,这样高频的锁请求就反而不利于系统性能的优化了,虽然单次同步操作的时间可能很短。锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗

  • 目标减少频繁加锁/解锁的开销

  • 原理

    • 将多个连续的锁操作合并为一个更大的同步块。

    • 典型场景:两块需要同步操作   和   循环体内反复加锁。

      //1.两块需要同步操作
      //锁粗化前
      public void doSomethingMethod(){
          synchronized(lock){
              //do some thing
          }
          //这是还有一些代码,做其它不需要同步的工作,但能很快执行完毕
          synchronized(lock){
              //do other thing
          }
      }
      //上面的代码是有两块需要同步操作的,但在这两块需要同步操作的代码之间,
      //需要做一些其它的工作,而这些工作只会花费很少的时间,那么我们就可以把
      //这些工作代码放入锁内,将两个同步代码块
      //合并成一个,以降低多次锁请求、同步、释放带来的系统性能消耗,合并后的代码如下:
      
      
      
      //锁粗化后
      public void doSomethingMethod(){
          //进行锁粗化:整合成一次锁请求、同步、释放
          synchronized(lock){
              //do some thing
              //做其它不需要同步但能很快执行完的工作
              //do other thing
          }
      }
      
      //注意:这样做是有前提的,就是中间不需要同步的代码能够很快速地完成,
      //如果不需要同步的代码需要花很长时间,就会导致同步块的执行需要花费很长的时间,
      //这样做也就不合理了。
      
      
      
      //2. 循环体内反复加锁
      //锁粗化前
      for(int i=0;i<size;i++){
          synchronized(lock){
          }
      }
      //上面代码每次循环都会进行锁的请求、同步与释放,看起来貌似没什么问题,
      //且在jdk内部会对这类代码锁的请求做一些优化,但是还不如把加锁代码写在
      //循环体的外面,这样一次锁的请求就可以达到我们的要求,除非有特殊的需要
      //:循环需要花很长时间,但其它线程等不起,要给它们执行的机会
      
      
      //锁粗化后
      synchronized(lock){
          for(int i=0;i<size;i++){
          }
      }
  • 优化效果:减少锁请求次数,但可能降低并发度。

  • 触发条件:JIT 检测到相邻同步块使用同一锁对象

案例1:在处理集合类时的锁优化示例
public class CollectionLockOptimization {
    private final List<String> list = new ArrayList<>();
    private final Object lock = new Object();
    
    // 优化前:频繁加锁解锁
    public void addItemsInefficient(String[] items) {
        for (String item : items) {
            synchronized (lock) {
                list.add(item);
                // 进行一些验证
                validateItem(item);
            }
        }
    }
    
    // 优化后:合并加锁操作
    public void addItemsEfficient(String[] items) {
        synchronized (lock) {
            for (String item : items) {
                list.add(item);
                // 进行一些验证
                validateItem(item);
            }
        }
    }
    
    private void validateItem(String item) {
        // 模拟验证逻辑
        if (item == null || item.isEmpty()) {
            throw new IllegalArgumentException("Invalid item");
        }
    }
    
    // 使用锁消除优化的本地操作
    public List<String> processLocalList(String[] items) {
        List<String> localList = new ArrayList<>();
        for (String item : items) {
            synchronized (item) {  // 这个锁可能被JVM消除
                localList.add(item);
            }
        }
        return localList;
    }
}
案例2:实现一个优化的缓存访问机制
public class CacheAccessOptimization<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    
    // 优化批量读取操作
    public List<V> batchRead(List<K> keys) {
        rwLock.readLock().lock();
        try {
            return keys.stream()
                .map(cache::get)
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        } finally {
            rwLock.readLock().unlock();
        }
    }
    
    // 优化批量写入操作
    public void batchWrite(Map<K, V> entries) {
        rwLock.writeLock().lock();
        try {
            entries.forEach(cache::put);
        } finally {
            rwLock.writeLock().unlock();
        }
    }
    
    // 使用锁消除优化的本地缓存操作
    public V processWithLocalCache(K key, V value) {
        Map<K, V> localCache = new HashMap<>();
        synchronized (localCache) {  // 这个锁可能被JVM消除
            localCache.put(key, value);
            return localCache.get(key);
        }
    }
}
 案例3:实现一个用于监控锁优化效果的工具
public class LockOptimizationMonitor {
    private final Map<String, LockStatistics> lockStats = new ConcurrentHashMap<>();
    
    public void monitorLockOperation(String lockId, Runnable operation) {
        LockStatistics stats = lockStats.computeIfAbsent(lockId, 
            k -> new LockStatistics());
        
        long startTime = System.nanoTime();
        operation.run();
        long duration = System.nanoTime() - startTime;
        
        stats.recordOperation(duration);
    }
    
    public void printStatistics() {
        lockStats.forEach((lockId, stats) -> {
            System.out.printf("Lock %s statistics:%n", lockId);
            System.out.printf("Average duration: %.2f ns%n", 
                stats.getAverageDuration());
            System.out.printf("Operation count: %d%n", 
                stats.getOperationCount());
        });
    }
    
    private static class LockStatistics {
        private final AtomicLong totalDuration = new AtomicLong(0);
        private final AtomicLong operationCount = new AtomicLong(0);
        
        public void recordOperation(long duration) {
            totalDuration.addAndGet(duration);
            operationCount.incrementAndGet();
        }
        
        public double getAverageDuration() {
            long count = operationCount.get();
            return count > 0 ? (double) totalDuration.get() / count : 0;
        }
        
        public long getOperationCount() {
            return operationCount.get();
        }
    }
}

最佳实践建议

  1. 优先考虑使用锁粗化来减少锁操作的次数,特别是在循环中的锁操作。

  2. 对于仅在方法内部使用的对象,考虑其是否可能被JVM进行锁消除优化。

  3. 使用适当的监控工具来评估锁优化的效果。

  4. 根据实际场景选择合适的锁优化策略,避免过度优化。

  案例4:遵循最佳实践的示例
public class LockOptimizationBestPractices {
    private final Object lock = new Object();
    private final List<String> data = new ArrayList<>();
    private final LockOptimizationMonitor monitor = new LockOptimizationMonitor();
    
    public void processData(List<String> items) {
        // 使用锁粗化优化
        monitor.monitorLockOperation("batch-process", () -> {
            synchronized (lock) {
                items.forEach(item -> {
                    // 在同一个锁内处理多个操作
                    data.add(item);
                    processItem(item);
                });
            }
        });
    }
    
    private void processItem(String item) {
        // 对于方法内的局部对象,JVM可能会进行锁消除
        StringBuilder sb = new StringBuilder();
        synchronized (sb) {
            sb.append("Processing: ").append(item);
        }
        // 处理逻辑
    }
}

4.4、批量重偏向与撤销(Bulk Rebiasing & Revocation)
4.4.1. 批量重偏向(Bulk Rebiasing)
  • 目标优化多线程交替使用偏向锁的场景

  • 原理

    • 当一个类的偏向锁被多个线程交替访问时,JVM 会批量将这些锁重偏向到新线程。

    • 阈值:默认当类的偏向锁撤销次数达到 20 次(BiasedLockingBulkRebiasThreshold)。

  • 优化效果:减少频繁撤销偏向锁的开销。

4.4.2. 批量撤销(Bulk Revocation)
  • 目标:彻底禁用不再适合偏向的类的偏向锁。

  • 原理

    • 当类的偏向锁撤销次数超过 40 次(BiasedLockingBulkRevokeThreshold),JVM 会禁用该类的偏向锁。

  • 优化效果:避免无效的偏向锁操作。


4.5、轻量级锁的快速获取与释放
  • 目标:优化无竞争场景下的轻量级锁操作。

  • 原理

    • 快速路径(Fast Path):若对象处于无锁状态,直接通过 CAS 获取轻量级锁。

    • 慢速路径(Slow Path):存在竞争时,走锁升级流程。

  • 优化效果:减少锁状态检查的开销。


4.6、锁膨胀策略
  • 目标:控制锁升级的触发条件。

  • 原理

    • 自旋阈值轻量级锁自旋失败次数超过阈值(默认 10 次),升级为重量级锁

    • 竞争检测:若短时间内多次触发锁竞争,直接使用重量级锁

  • 优化效果避免在高竞争场景下过度自旋


4.7、优化对比与适用场景
优化技术适用场景性能收益实现复杂度
偏向锁单线程独占接近无锁性能
轻量级锁多线程交替执行减少上下文切换
自旋锁锁持有时间短避免线程阻塞
锁消除对象未逃逸完全消除同步开销
锁粗化连续同步块减少锁操作次数

五、常见问题与解答

1. synchronized 是公平锁吗?
  • synchronized 是非公平锁,新线程可直接竞争锁,可能导致线程饥饿。

2. 锁升级是否可逆?
  • 偏向锁可撤销:当发生竞争时,偏向锁可降级为无锁状态。

  • 重量级锁不可逆:一旦升级为重量级锁,无法回退到轻量级锁。

3. synchronized 和 ReentrantLock 的区别?
特性synchronizedReentrantLock
锁类型非公平锁(默认)可选择公平或非公平锁
锁释放自动释放(代码块结束/异常)需手动调用 unlock()
条件变量通过 wait()/notify()支持多个 Condition 对象
性能JDK6 后优化后接近高竞争场景下更灵活

六、总结

  • 核心机制synchronized 通过 对象头 Mark Word 和 Monitor 模型 实现锁的获取与释放。

  • 锁升级无锁 → 偏向锁 → 轻量级锁 → 重量级锁,根据竞争动态优化性能。

  • JVM 优化:自旋锁、锁消除、锁粗化等策略减少同步开销。

  • 适用场景:简单同步需求优先使用 synchronized复杂场景(如超时、公平锁)选择 ReentrantLock

理解 synchronized 的原理与优化,有助于编写高效、线程安全的并发代码。

参考 一文彻底搞懂synchronized实现原理_synchronized的实现原理-CSDN博客

Java面试要点92 - Java锁粗化与锁消除解析_锁消除和锁粗化-CSDN博客

相关文章:

  • Ubuntu “文件系统根目录”上的磁盘空间不足
  • 数据结构篇——线索二叉树
  • 【R语言】lm线性回归及输出含义,置信区间,预测,R方,ggplot 拟合直线
  • Unity学习之Shader总结(一)
  • Ubuntu20.04安装Nvidia显卡驱动
  • Cursor与Blender-MCP生成3D模型
  • Spring Boot集成MyBatis与MySQL
  • linux du和df
  • Linux 快捷键 | 终端快捷键 / 键盘快捷键
  • 大模型如何赋能安全防御?威胁检测与漏洞挖掘的“AI革命”
  • Linux 命令行整理(完善中)
  • fastapi+angular实现菜鸟驿站系统
  • 安全地自动重新启动 Windows 资源管理器Bat脚本
  • HTML 样式与布局初体验:学习进程中的关键节点(一)
  • 在 VSCode 远程开发环境下使用 Git 常用命令
  • Spring Boot - Spring Boot 静态资源映射(默认静态资源映射、自定义静态资源映射)
  • pytorch小记(十三):pytorch中`nn.ModuleList` 详解
  • 什么是站群服务器?站群服务器应该怎么选?
  • (暴力枚举 水题 长度为3的不同回文子序列)leetcode 1930
  • 可视化图解算法:链表中倒数(最后)k个结点
  • 美国第一季度经济环比萎缩0.3%,特朗普:怪拜登,与关税无关
  • 内蒙古公开宣判144件毁林毁草刑案,单起非法占用林地逾250亩
  • 聚焦各领域顶尖工匠,《上海工匠》第十季于五一播出
  • 欢迎回家!神十九返回舱成功着陆
  • 辽宁辽阳市白塔区一饭店火灾事故举行新闻发布会,现场为遇难者默哀
  • 何立峰出席驻沪中央金融机构支持上海建设国际金融中心座谈会并讲话