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

线程安全与并发锁:synchronized vs ReentrantLock——面试必问!

一、问题现场还原

那是一个周五的下午,小王正在写一个计数器:

public class Counter { private int count = 0; public void increment() { count++; // 自增 } public int getCount() { return count; } }

测试代码:

public class CounterTest { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); // 创建1000个线程,每个线程自增1000次 for (int i = 0; i < 1000; i++) { new Thread(() -> { for (int j = 0; j < 1000; j++) { counter.increment(); } }).start(); } Thread.sleep(3000); // 等待所有线程完成 // 期望结果:1000 * 1000 = 1000000 // 实际结果:可能是 998456、998789、999234... System.out.println("最终结果:" + counter.getCount()); } }

问题分析:为什么结果不对?

count++ 实际上分为三步: 1. 读取count的值 2. count + 1 3. 写入新的值 线程A和线程B同时执行: ┌─────────┬─────────┐ │ 线程A │ 线程B │ ├─────────┼─────────┤ │ 读: 0 │ │ │ │ 读: 0 │ ← 读取的都是0 │ 写: 1 │ │ │ │ 写: 1 │ ← 都写入1 └─────────┴─────────┘ 结果:应该是2,实际是1

二、解决方案:使用锁

2.1 synchronized关键字

public class Counter { private int count = 0; // 方法锁 public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }

或者:

public class Counter { private int count = 0; private final Object lock = new Object(); // 锁对象 public void increment() { synchronized (lock) { // 代码块锁 count++; } } public int getCount() { synchronized (lock) { return count; } } }

2.2 ReentrantLock

import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); // 必须在finally中释放 } } public int getCount() { lock.lock(); try { return count; } finally { lock.unlock(); } } }

三、synchronized vs ReentrantLock对比

维度synchronizedReentrantLock
实现方式JVM层面,关键字API层面,类
锁类型非公平锁可选公平/非公平锁
获取锁自动释放必须手动释放
超时不支持支持
可中断不支持支持
条件变量1个(wait/notify)多个(Condition)
性能JDK 1.6后优化差不多
使用场景简单场景复杂场景

四、synchronized详解

4.1 三种使用方式

public class SynchronizedDemo { // 1. 实例方法锁(锁住当前对象) public synchronized void method1() { // 代码 } // 2. 静态方法锁(锁住Class对象) public static synchronized void method2() { // 代码 } // 3. 代码块锁(锁住指定对象) private final Object lock = new Object(); public void method3() { synchronized (lock) { // 代码 } } }

4.2 锁升级(JDK 1.6优化)

无锁 → 偏向锁 → 轻量级锁 → 重量级锁 无锁:没有线程竞争 偏向锁:只有一个线程访问,自动偏向 轻量级锁:少量线程竞争,CAS自旋 重量级锁:大量线程竞争,等待

4.3 synchronized的缺点

// ❌ 缺点1:无法超时 public void method() { synchronized (this) { // 如果获取不到锁,会一直等待 } } // ❌ 缺点2:无法中断 public void method() { synchronized (this) { // 等待期间无法被中断 } } // ❌ 缺点3:只能有一个Condition public class ProducerConsumer { private int count = 0; public synchronized void produce() throws InterruptedException { while (count >= 10) { wait(); // 生产者等待 } count++; notifyAll(); // 通知消费者 } public synchronized void consume() throws InterruptedException { while (count <= 0) { wait(); // 消费者等待 } count--; notifyAll(); // 通知生产者 } // 问题:notifyAll会唤醒所有线程(包括其他不相关的) }

五、ReentrantLock详解

5.1 基础用法

public class ReentrantLockDemo { private final ReentrantLock lock = new ReentrantLock(); public void method() { lock.lock(); try { // 业务代码 } finally { lock.unlock(); // 必须在finally中释放 } } }

5.2 公平锁 vs 非公平锁

// 非公平锁(默认):性能高,可能饥饿 ReentrantLock lock1 = new ReentrantLock(false); // 公平锁:性能略低,保证公平性 ReentrantLock lock2 = new ReentrantLock(true);

5.3 tryLock(超时获取)

public boolean tryLockWithTimeout() throws InterruptedException { // 尝试获取锁,最多等待1秒 if (lock.tryLock(1, TimeUnit.SECONDS)) { try { // 获取成功 return true; } finally { lock.unlock(); } } else { // 获取失败 return false; } }

5.4 lockInterruptibly(可中断)

public void method() throws InterruptedException { lock.lockInterruptibly(); // 可中断的获取锁 try { // 业务代码 } finally { lock.unlock(); } }

5.5 Condition(条件变量)

public class ProducerConsumerWithLock { private final ReentrantLock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); // 未满条件 private final Condition notEmpty = lock.newCondition(); // 非空条件 private int count = 0; public void produce() throws InterruptedException { lock.lock(); try { while (count >= 10) { notFull.await(); // 等待未满 } count++; System.out.println("生产:" + count); notEmpty.signal(); // 通知消费者 } finally { lock.unlock(); } } public void consume() throws InterruptedException { lock.lock(); try { while (count <= 0) { notEmpty.await(); // 等待非空 } count--; System.out.println("消费:" + count); notFull.signal(); // 通知生产者 } finally { lock.unlock(); } } }

六、性能对比

6.1 基准测试

public class LockPerformanceTest { private static final int THREAD_COUNT = 10; private static final int INCREMENT_COUNT = 1000000; @Test public void testSynchronized() throws InterruptedException { Counter counter = new SynchronizedCounter(); long start = System.currentTimeMillis(); for (int i = 0; i < THREAD_COUNT; i++) { new Thread(() -> { for (int j = 0; j < INCREMENT_COUNT; j++) { counter.increment(); } }).start(); } Thread.sleep(5000); System.out.println("synchronized: " + (System.currentTimeMillis() - start) + "ms"); } @Test public void testReentrantLock() throws InterruptedException { Counter counter = new ReentrantLockCounter(); long start = System.currentTimeMillis(); for (int i = 0; i < THREAD_COUNT; i++) { new Thread(() -> { for (int j = 0; j < INCREMENT_COUNT; j++) { counter.increment(); } }).start(); } Thread.sleep(5000); System.out.println("ReentrantLock: " + (System.currentTimeMillis() - start) + "ms"); } }

结果(JDK 1.8):

  • synchronized:1200ms
  • ReentrantLock:1150ms
  • 性能差不多

七、使用建议

使用synchronized: ✅ 简单场景 ✅ 不需要高级功能(超时、可中断、多Condition) ✅ 代码简洁 使用ReentrantLock: ✅ 需要公平锁 ✅ 需要超时获取锁 ✅ 需要可中断 ✅ 需要多个Condition ✅ 需要获取锁状态

八、常见问题

Q1:为什么synchronized不需要手动释放锁?

// synchronized由JVM自动管理 // 方法结束或异常时,JVM自动释放锁 public synchronized void method() { // 即使抛异常,也会自动释放 if (someCondition) { throw new RuntimeException(); } } // ReentrantLock必须手动释放 public void method() { lock.lock(); try { if (someCondition) { throw new RuntimeException(); } } finally { lock.unlock(); // 必须在finally中释放 } }

Q2:什么是锁的可见性?

// 可见性:一个线程修改了变量,其他线程能立即看到 public class VisibilityDemo { private boolean flag = false; public void setFlag() { flag = true; // 线程A修改 } public boolean getFlag() { return flag; // 线程B可能看不到修改! } } // 解决方案:使用volatile或synchronized public class VisibilityDemo { private volatile boolean flag = false; // volatile保证可见性 // 或者 public synchronized void setFlag() { flag = true; } public synchronized boolean getFlag() { return flag; } }

九、总结

今天我们学到了:

要点说明
线程安全问题多线程并发访问共享变量,导致结果不一致
synchronized关键字,JVM实现,简单易用
ReentrantLockAPI实现,功能强大,使用复杂
核心区别自动释放vs手动释放、1个Conditionvs多个Condition
性能JDK 1.6后两者性能差不多
选择简单场景用synchronized,复杂场景用ReentrantLock

今日互动

你在项目中遇到过线程安全问题吗?是用synchronized还是ReentrantLock?

http://www.jsqmd.com/news/689291/

相关文章:

  • Kyoo高级字幕支持:SSA/ASS格式与嵌入式字体完美呈现
  • Docker一键部署SearXNG:打造个人隐私搜索引擎(附国内镜像加速配置)
  • 别再只盯着YOLO了!用OpenCV+Python,基于RGB颜色阈值5步搞定简易火焰检测
  • OpenDrop:重新定义微观世界的开源数字微流控平台
  • 20260421 模拟赛
  • 别再只看图了!代谢组学OPLS-DA分析,R2Y和Q2Y到底怎么看才不踩坑?
  • 校园综合体育赛事自动化调度平台
  • GanttProject:开源项目管理工具深度探索
  • UDOP-large部署教程:HTTP端口7860访问异常排查与容器日志定位方法
  • Phi-3.5-mini-instruct中文场景深度适配:专有名词识别、成语理解、口语化表达强化
  • SCP单细胞分析:从原始数据到生物学洞见的完整解决方案
  • DuckLake变更数据捕获:实时监控数据变化的完整教程
  • C程序员必读:2026年3大内存漏洞(UAF、溢出、未初始化)在Linux/Kubernetes环境中的实时拦截方案
  • 做题随笔2
  • 中兴光猫配置解密工具:高效配置管理解决方案
  • Vue3项目实战:用vis-network从零搭建一个可自定义节点图标与连线的知识图谱
  • not-so-smart-contracts:GiftBox蜜罐合约的欺骗机制
  • 如何让你的Windows任务栏瞬间变透明?TranslucentTB深度体验指南
  • 对于高并发应用,文件 Session 是性能瓶颈。
  • 基于微信小程序实现电影院订票选座管理系统【附项目源码+论文说明】计算机毕业设计
  • 孩子坐不住?专注力训练可尝试这些互动学习方式 - 品牌测评鉴赏家
  • 2026年防爆电机及机械主轴企业最新推荐榜:防爆电机/机械主轴/仓壁振动器等设备供应 - 海棠依旧大
  • Hitchhiker团队协作功能深度指南:如何实现API开发无缝协作
  • 从零到三层互通:用Wireshark抓包带你理解VXLAN跨子网转发全过程
  • 别再死记硬背PID公式了!用这个水槽模型,5分钟搞懂P、I、D到底在干啥
  • 从Git SSL报错到HTTPS原理:手把手教你用OpenSSL诊断并修复证书链问题
  • 家有小学生必看!在家学同步教材,这4类工具刚需不踩坑 - 品牌测评鉴赏家
  • 没搞清楚这组概念之前,先别碰你的毕业论文——实测好写作AI降重降AIGC“三步闭环法”
  • 为什么越来越多女性创业者选择“玫瑰工程”?一个运营十五年的社区健康品牌深度解析 - 速递信息
  • utron与其他Go框架对比:为什么选择这个轻量级MVC方案