news 2026/4/23 16:57:30

Synchronized锁升级流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Synchronized锁升级流程

文章目录

    • 引言
    • 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等更灵活的机制。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 14:38:04

Llama-Factory LoRA微调实战指南

Llama-Factory LoRA微调实战指南 在大模型时代&#xff0c;我们不再只是使用通用语言模型来回答问题或写文章。越来越多的场景要求模型具备特定领域的知识表达能力——比如医疗问答、法律文书生成、金融摘要提取等。全参数微调虽然效果好&#xff0c;但动辄上百GB显存的需求让…

作者头像 李华
网站建设 2026/4/23 14:04:38

LobeChat能否冥想引导?心理健康关怀助手

LobeChat能否冥想引导&#xff1f;心理健康关怀助手 在快节奏的现代生活中&#xff0c;焦虑、失眠和情绪波动已成为许多人的日常困扰。人们开始寻求更便捷、私密且可持续的心理支持方式——而AI正悄然成为那个“随时在线”的倾听者与陪伴者。 想象这样一个场景&#xff1a;深夜…

作者头像 李华
网站建设 2026/4/23 13:23:59

29、软件编译配置工具:pkg-config 与 GNU 自动工具使用指南

软件编译配置工具:pkg-config 与 GNU 自动工具使用指南 1. pkg-config 工具介绍 pkg-config 是一个非常实用的工具,借助它,我们能通过一个命令获取软件包的诸多关键信息,像名称、版本、安装路径、依赖关系以及编译器选项等。 在使用 pkg-config 之前,要保证系统中所有包…

作者头像 李华
网站建设 2026/4/14 6:50:45

32、GConf 开发全解析:从基础到实战应用

GConf 开发全解析:从基础到实战应用 1. 引言 在软件开发中,配置管理是一个重要的环节。GConf 作为 GNOME 桌面环境下常用的配置管理系统,为应用程序提供了方便的配置存储和管理方式。本文将深入探讨 GConf 的多个方面,包括值变更通知、缓存操作、错误处理、模式管理等,并…

作者头像 李华
网站建设 2026/4/19 13:31:59

飞桨PaddlePaddle入门与核心模块解析

飞桨PaddlePaddle深度学习实战&#xff1a;从张量到模型训练的完整路径 在人工智能技术飞速发展的今天&#xff0c;深度学习已不再是实验室里的神秘黑箱&#xff0c;而是驱动智能推荐、图像识别、语音助手等日常应用的核心动力。面对这一趋势&#xff0c;开发者需要一个既能支撑…

作者头像 李华
网站建设 2026/4/20 8:49:58

小白也能学会的YOLO-V5目标检测训练全指南

小白也能学会的YOLO-V5目标检测训练全指南 你有没有过这样的经历&#xff1a;看到别人用AI识别图片中的猫狗、车辆甚至工业零件&#xff0c;心里跃跃欲试&#xff0c;但一打开“深度学习”、“卷积神经网络”这些术语就头大&#xff1f; 或者好不容易鼓起勇气点开GitHub项目&a…

作者头像 李华