news 2026/4/23 21:40:59

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
线程安全与并发锁: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?

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

暗黑2重制版终极自动化脚本Botty:5步配置实现24小时高效MF

暗黑2重制版终极自动化脚本Botty&#xff1a;5步配置实现24小时高效MF 【免费下载链接】botty D2R Pixel Bot 项目地址: https://gitcode.com/gh_mirrors/bo/botty 还在为重复刷怪感到枯燥乏味吗&#xff1f;Botty作为专业的暗黑2重制版像素级自动化脚本&#xff0c;能够…

作者头像 李华
网站建设 2026/4/23 21:36:57

Phi-3.5-mini-instruct高算力适配:消费级显卡上的专业级推理体验

Phi-3.5-mini-instruct高算力适配&#xff1a;消费级显卡上的专业级推理体验 1. 模型概述 Phi-3.5-mini-instruct是微软推出的轻量级指令微调大语言模型&#xff0c;采用Transformer解码器架构&#xff0c;支持128K超长上下文窗口。这个3.8B参数的模型在多语言对话、代码生成…

作者头像 李华
网站建设 2026/4/23 21:36:16

从Git SSL报错到HTTPS原理:手把手教你用OpenSSL诊断并修复证书链问题

从Git SSL报错到HTTPS原理&#xff1a;手把手教你用OpenSSL诊断并修复证书链问题 当你兴致勃勃地准备克隆一个Git仓库时&#xff0c;突然遭遇unable to get local issuer certificate的报错&#xff0c;这就像在高速公路上突然被拦下检查证件&#xff0c;却发现自己的身份证不…

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

Android-OCR核心架构解析:从ZXing到Tesseract的完美融合

Android-OCR核心架构解析&#xff1a;从ZXing到Tesseract的完美融合 【免费下载链接】android-ocr Experimental optical character recognition app 项目地址: https://gitcode.com/gh_mirrors/an/android-ocr Android-OCR是一款实验性光学字符识别应用&#xff0c;通过…

作者头像 李华