Synchronized锁升级流程
2026/3/26 13:07:11 网站建设 项目流程

文章目录

    • 引言
    • synchronized的基本使用
      • 同步方法
      • 同步代码块
    • synchronized的底层原理
      • 字节码层面分析
      • 对象头与Mark Word
    • 锁升级优化过程
      • 1. 偏向锁(Biased Locking)
      • 2. 轻量级锁(Lightweight Locking)
      • 3. 重量级锁(Heavyweight Locking)
    • 实战中的最佳实践
      • 1. 锁粒度控制
      • 2. 避免死锁
      • 3. 双检锁单例模式
    • 性能优化建议
      • 1. 减少锁持有时间
      • 2. 使用读写锁替代
    • 常见问题与解决方案
      • 1. synchronized与Lock的区别
      • 2. 如何选择锁策略
    • 总结

引言

线程安全是我们必须面对的核心挑战之一。Java为我们提供了synchronized关键字。

synchronized的基本使用

同步方法

同步方法是最简单的使用方式,直接在方法声明中添加synchronized关键字即可:

publicclassCounter{privateintcount=0;// 同步实例方法publicsynchronizedvoidincrement(){count++;}// 同步静态方法publicstaticsynchronizedvoidstaticIncrement(){// 静态方法的锁是类的Class对象}}

代码说明:

  • 实例方法的锁是当前对象实例(this)
  • 静态方法的锁是当前类的Class对象
  • 同步方法保证了同一时间只有一个线程能执行该方法

同步代码块

同步代码块提供了更细粒度的控制,可以指定锁对象:

publicclassOrderService{privatefinalObjectlock=newObject();privateMap<String,Integer>inventory=newHashMap<>();publicvoidprocessOrder(StringproductId){// 非同步代码,可以并发执行System.out.println("开始处理订单...");synchronized(lock){// 同步代码块,保证库存操作的原子性Integerstock=inventory.get(productId);if(stock!=null&&stock>0){inventory.put(productId,stock-1);System.out.println("扣减库存成功");}}// 后续非同步操作System.out.println("订单处理完成");}}

代码说明:

  • 可以指定任意对象作为锁
  • 锁的范围更小,性能更好
  • 提供了更灵活的同步控制

synchronized的底层原理

字节码层面分析

让我们通过反编译来看看synchronized在字节码层面是如何实现的:

publicclassSynchronizedDemo{privatestaticintcounter=0;privatefinalObjectlock=newObject();publicvoidsyncMethod(){synchronized(this){counter++;}}}

使用javap -c SynchronizedDemo.class反编译后,可以看到关键字节码:

public void syncMethod(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter // 进入同步块 4: getstatic #2 // 获取counter 7: iconst_1 8: iadd 9: putstatic #2 // 设置counter 12: aload_1 13: monitorexit // 正常退出同步块 14: goto 22 17: astore_2 18: aload_1 19: monitorexit // 异常退出同步块 20: aload_2 21: athrow 22: return

关键点解析:

  • monitorenter:获取对象的监视器锁
  • monitorexit:释放对象的监视器锁
  • 编译器会自动生成异常处理,确保锁一定会被释放

对象头与Mark Word

在HotSpot虚拟机中,每个对象都有一个对象头,其中包含Mark Word,它记录了对象的锁状态信息:

锁状态存储内容标志位
无锁对象哈希码、分代年龄01
偏向锁线程ID、Epoch、分代年龄01
轻量级锁指向栈中锁记录的指针00
重量级锁指向互斥量(monitor)的指针10
GC标记11

锁升级优化过程

JDK 1.6之后,synchronized引入了锁升级机制来优化性能:

1. 偏向锁(Biased Locking)

publicclassBiasedLockExample{privatestaticfinalObjectlock=newObject();privatestaticintcount=0;publicstaticvoidmain(String[]args)throwsInterruptedException{// 默认情况下,JVM会延迟开启偏向锁Thread.sleep(5000);// 等待偏向锁开启synchronized(lock){count++;System.out.println("第一次获取锁,应该是偏向锁");}}}

偏向锁特点:

  • 适用于只有一个线程访问同步块的场景
  • 在对象头中记录线程ID
  • 同一个线程再次获取锁时不需要CAS操作

2. 轻量级锁(Lightweight Locking)

当有第二个线程尝试获取锁时,偏向锁会升级为轻量级锁:

publicclassLightweightLockExample{privatestaticfinalObjectlock=newObject();publicstaticvoidmain(String[]args){// 线程1newThread(()->{synchronized(lock){try{Thread.sleep(100);// 短暂持有锁}catch(InterruptedExceptione){e.printStackTrace();}}}).start();// 线程2 - 会触发锁升级newThread(()->{try{Thread.sleep(10);// 确保线程1先获取锁}catch(InterruptedExceptione){e.printStackTrace();}synchronized(lock){System.out.println("线程2获取锁,此时应该是轻量级锁");}}).start();}}

轻量级锁特点:

  • 使用CAS操作替代操作系统互斥量
  • 适用于线程交替执行的场景
  • 自旋等待避免线程切换开销

3. 重量级锁(Heavyweight Locking)

当竞争激烈时,轻量级锁会升级为重量级锁:

publicclassHeavyweightLockExample{privatestaticfinalObjectlock=newObject();privatestaticfinalintTHREAD_COUNT=10;publicstaticvoidmain(String[]args){CountDownLatchlatch=newCountDownLatch(THREAD_COUNT);for(inti=0;i<THREAD_COUNT;i++){newThread(()->{synchronized(lock){try{// 模拟业务处理Thread.sleep(50);}catch(InterruptedExceptione){e.printStackTrace();}}latch.countDown();}).start();}try{latch.await();System.out.println("所有线程执行完成,经历了锁升级过程");}catch(InterruptedExceptione){e.printStackTrace();}}}

重量级锁特点:

  • 使用操作系统的互斥量(Mutex)
  • 线程会进入阻塞状态
  • 适用于高竞争场景

实战中的最佳实践

1. 锁粒度控制

在商城项目中,库存管理需要特别注意锁的粒度:

publicclassInventoryManager{// 不好的做法:锁粒度太粗privatefinalObjectglobalLock=newObject();privateMap<String,Integer>inventory=newConcurrentHashMap<>();// 好的做法:细粒度锁privatefinalMap<String,Object>productLocks=newConcurrentHashMap<>();publicvoidupdateStock(StringproductId,intquantity){// 获取商品特定的锁ObjectproductLock=productLocks.computeIfAbsent(productId,k->newObject());synchronized(productLock){IntegercurrentStock=inventory.getOrDefault(productId,0);inventory.put(productId,currentStock+quantity);}}publicbooleanpurchase(StringproductId,intquantity){ObjectproductLock=productLocks.computeIfAbsent(productId,k->newObject());synchronized(productLock){IntegercurrentStock=inventory.get(productId);if(currentStock==null||currentStock<quantity){returnfalse;}inventory.put(productId,currentStock-quantity);returntrue;}}}

2. 避免死锁

在营销系统的奖品发放中,要特别注意避免死锁:

publicclassPrizeDistribution{privatefinalObjectprizeLock=newObject();privatefinalObjectuserLock=newObject();// 错误的做法:可能产生死锁publicvoiddistributePrizeWrong(longuserId,StringprizeId){synchronized(prizeLock){synchronized(userLock){// 处理奖品发放}}}// 正确的做法:固定锁顺序publicvoiddistributePrizeRight(longuserId,StringprizeId){// 按照固定顺序获取锁ObjectfirstLock,secondLock;if(System.identityHashCode(prizeLock)<System.identityHashCode(userLock)){firstLock=prizeLock;secondLock=userLock;}else{firstLock=userLock;secondLock=prizeLock;}synchronized(firstLock){synchronized(secondLock){// 安全的奖品发放逻辑System.out.println("为用户"+userId+"发放奖品"+prizeId);}}}}

3. 双检锁单例模式

在项目配置管理中,单例模式经常使用:

publicclassConfigManager{// volatile保证可见性和禁止指令重排序privatestaticvolatileConfigManagerinstance;privateConfigManager(){// 私有构造函数}publicstaticConfigManagergetInstance(){if(instance==null){// 第一次检查synchronized(ConfigManager.class){if(instance==null){// 第二次检查instance=newConfigManager();}}}returninstance;}}

为什么需要volatile:

  • 防止指令重排序
  • 保证多线程环境下的可见性
  • 避免其他线程看到未完全初始化的对象

性能优化建议

1. 减少锁持有时间

publicclassOptimizedOrderProcessor{privateMap<String,BigDecimal>prices=newHashMap<>();privateMap<String,Integer>stock=newHashMap<>();// 优化前:锁持有时间过长publicBigDecimalcalculateTotalBad(List<String>products){synchronized(this){BigDecimaltotal=BigDecimal.ZERO;for(Stringproduct:products){// 模拟耗时操作try{Thread.sleep(10);}catch(InterruptedExceptione){e.printStackTrace();}total=total.add(prices.getOrDefault(product,BigDecimal.ZERO));}returntotal;}}// 优化后:只锁必要的部分publicBigDecimalcalculateTotalGood(List<String>products){// 先收集需要的数据(不需要同步)List<BigDecimal>priceList=newArrayList<>();for(Stringproduct:products){// 模拟耗时操作try{Thread.sleep(10);}catch(InterruptedExceptione){e.printStackTrace();}}// 同步计算总和synchronized(this){BigDecimaltotal=BigDecimal.ZERO;for(Stringproduct:products){total=total.add(prices.getOrDefault(product,BigDecimal.ZERO));}returntotal;}}}

2. 使用读写锁替代

对于读多写少的场景,考虑使用ReentrantReadWriteLock

publicclassProductCache{privatefinalMap<String,Product>cache=newHashMap<>();privatefinalReentrantReadWriteLockrwLock=newReentrantReadWriteLock();publicProductgetProduct(Stringid){rwLock.readLock().lock();// 获取读锁try{returncache.get(id);}finally{rwLock.readLock().unlock();}}publicvoidupdateProduct(Productproduct){rwLock.writeLock().lock();// 获取写锁try{cache.put(product.getId(),product);}finally{rwLock.writeLock().unlock();}}}

常见问题与解决方案

1. synchronized与Lock的区别

特性synchronizedReentrantLock
实现机制JVM层面实现JDK层面实现
锁获取自动获取释放手动获取释放
可中断不支持支持
公平锁非公平可选公平/非公平
条件变量有限支持灵活支持

2. 如何选择锁策略

根据实际场景选择合适的同步机制:

publicclassLockStrategySelector{/** * 根据场景选择锁策略 * @param scenario 场景描述 * @return 建议的锁策略 */publicStringselectLockStrategy(Stringscenario){switch(scenario){case"简单同步":return"使用synchronized,简单可靠";case"需要超时":return"使用ReentrantLock.tryLock()";case"读写分离":return"使用ReentrantReadWriteLock";case"高并发统计":return"考虑使用LongAdder";case"分布式环境":return"使用分布式锁如Redis锁";default:return"使用synchronized";}}}

总结

synchronizedJava内置的同步机制,从最初的重量级锁发展到现在的智能锁升级,性能已经得到了极大的优化。在实际项目中,需要根据具体场景选择合适的同步策略:对于简单的同步需求,synchronized是选择;对于复杂的并发控制,可以考虑ReentrantLock等更灵活的机制。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询