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

〈Java高并发核心编程·卷2〉(第2篇)—— Java内置锁与线程安全

🧩 前言:为什么锁是多线程的 “交通信号灯”?

在多线程的世界里,共享资源就像一条没有信号灯的十字路口 —— 多个线程同时争抢时,必然会发生 “交通事故”(数据错乱、结果不可预测)。而 synchronized 作为 Java 内置的 “交通信号灯”,能强制让线程按序访问共享资源,是保障线程安全的核心机制。

本篇将从 线程安全根源→synchronized 全场景用法→锁底层演进→经典实战模型,带大家彻底掌握 Java 内置锁的核心知识,同时覆盖 80% 高频面试考点。


一、线程安全:多线程的 “达摩克利斯之剑”

线程安全是多线程开发的核心痛点,也是面试必问的基础概念。很多初学者误以为 “多线程 = 线程不安全”,其实线程安全的本质是 “共享资源的访问控制”。

1.1 线程不安全的核心原因:非原子操作 + 共享变量

线程不安全的根源可总结为 “三要素”:

  • 共享变量:多个线程能访问的可变变量(如静态变量、实例变量)
  • 非原子操作:操作无法一次性完成(如 count++、i = i + 1)
  • 并发执行:多个线程同时执行非原子操作
📌 经典反例:为什么 count++ 会导致结果错乱?

public class UnsafeCountDemo {// 共享变量:多个线程可访问的静态变量private static int count = 0;public static void main(String[] args) throws InterruptedException {// 启动10个线程,每个线程执行1000次count++for (int i = 0; i < 10; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) {count++; // 非原子操作:包含3个步骤}}).start();}// 等待所有线程执行完成Thread.sleep(2000);System.out.println("最终结果:" + count); // 预期10000,实际常小于10000}
}
底层拆解:count++ 的非原子性

count++ 在 JVM 层面会被拆分为 3 个独立指令,线程执行时可能被 “打断”:

  1. 读取(Load):从主内存读取 count 的当前值到线程工作内存
  1. 加一(Add):在线程工作内存中对 count 执行 +1 操作
  1. 写回(Store):将更新后的值写回主内存
并发冲突场景:

假设线程 A 和线程 B 同时执行 count++,初始 count=0:

  • 线程 A 读取 count=0,还未加一;线程 B 也读取 count=0
  • 线程 A 加一得 1,写回主内存;线程 B 加一也得 1,写回主内存
  • 最终 count=1,而非预期的 2—— 这就是 “线程安全问题”

1.2 线程安全的定义:结果可预测 + 符合业务逻辑

一个程序在多线程环境下安全,需满足两个条件:

  1. 原子性:关键操作(如更新共享变量)不可拆分,要么全执行,要么全不执行
  1. 可见性:一个线程对共享变量的修改,其他线程能立即看到(避免工作内存缓存导致的 “数据不一致”)
  1. 有序性:程序执行顺序符合代码逻辑,避免 JVM 指令重排序导致的 “逻辑错乱”

💡 面试延伸:synchronized 能保证什么?

答:synchronized 能同时保证原子性、可见性和有序性 —— 这也是它成为 “万能锁” 的核心原因。


二、临界区与同步机制:解决线程安全的 “钥匙”

要解决线程安全问题,本质是控制 “临界区” 的访问 —— 让多个线程 “排队” 进入临界区,避免并发冲突。

2.1 临界区:线程安全问题的 “高发区”

临界区(Critical Section)是指包含 “共享资源读写操作” 的代码段。例如:

public void updateCount() {// 非临界区:无共享资源操作int temp = 1;// 临界区:包含共享变量count的写操作count += temp;// 非临界区:无共享资源操作System.out.println(count);
}

2.2 同步机制:控制临界区访问的 “规则”

常见的同步机制有三种,synchronized 是 Java 原生支持、无需额外依赖的 “内置锁”:

同步机制

核心特点

适用场景

synchronized

内置锁、自动释放、可重入

大多数业务场景(简单、稳定)

ReentrantLock

显式锁、可中断、可超时

复杂场景(如公平锁、条件变量)

volatile

保证可见性和有序性,不保证原子性

仅读或单线程写的共享变量


三、synchronized 全场景用法:从基础到进阶

synchronized 有三种用法,核心区别在于 “锁对象” 不同 —— 锁对象决定了 “哪些线程会互斥”(持有同一把锁的线程才会排队)。

3.1 用法 1:同步实例方法(锁对象 = 当前实例 this)

代码实现(带场景说明)
/*** 同步实例方法:锁对象为当前类的实例(this)* 特点:同一实例的多个同步方法会互斥,不同实例的方法不会互斥*/
public class SyncInstanceMethodDemo {private int count = 0;// 同步实例方法:锁对象 = this(当前SyncInstanceMethodDemo实例)public synchronized void increment() {count++;System.out.printf("[%s] 执行increment,count=%d%n", Thread.currentThread().getName(), count);}// 同一实例的另一个同步方法:锁对象同样是thispublic synchronized void decrement() {count--;System.out.printf("[%s] 执行decrement,count=%d%n", Thread.currentThread().getName(), count);}public static void main(String[] args) {// 场景1:同一实例的两个同步方法SyncInstanceMethodDemo demo1 = new SyncInstanceMethodDemo();new Thread(() -> {for (int i = 0; i < 3; i++) demo1.increment();}, "线程A").start();new Thread(() -> {for (int i = 0; i < 3; i++) demo1.decrement();}, "线程B").start();// 结果:线程A和线程B会互斥(因为用同一把锁this),count不会错乱// 场景2:不同实例的同步方法SyncInstanceMethodDemo demo2 = new SyncInstanceMethodDemo();new Thread(() -> {for (int i = 0; i < 3; i++) demo2.increment();}, "线程C").start();// 结果:线程C与线程A/B不会互斥(因为锁对象是demo2,与demo1不同)}
}

📌 核心注意点:
  • 锁对象是 “实例”,而非 “方法”—— 同一实例的所有同步实例方法共享同一把锁
  • 适合保护 “实例级共享变量”(如上述代码中的 count)
  • 缺点:若实例被频繁创建,锁的粒度会过细,可能导致 “锁失效”(不同实例的线程不互斥)

3.2 用法 2:同步代码块(锁对象 = 自定义对象,灵活度最高)

同步代码块是 synchronized 最灵活的用法,可指定任意对象作为锁,且能精准保护 “部分临界区”(避免整个方法加锁导致的性能损耗)。

代码实现(带优化技巧)
/*** 同步代码块:锁对象可自定义,仅保护临界区代码* 核心优势:1. 锁对象灵活 2. 减小锁粒度(提升性能)*/
public class SyncBlockDemo {// 实例级共享变量private int instanceCount = 0;// 静态共享变量private static int staticCount = 0;// 自定义锁对象(推荐:private final修饰,避免锁对象被修改导致锁失效)private final Object instanceLock = new Object();private static final Object staticLock = new Object();public void updateInstanceCount() {// 非临界区:无需加锁(如参数校验、局部变量操作)int temp = 1;System.out.printf("[%s] 准备更新实例变量%n", Thread.currentThread().getName());// 同步代码块1:保护实例变量(锁对象=instanceLock)synchronized (instanceLock) {instanceCount += temp;System.out.printf("[%s] 实例变量count=%d%n", Thread.currentThread().getName(), instanceCount);}// 非临界区:无需加锁try {Thread.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();}}public void updateStaticCount() {// 同步代码块2:保护静态变量(锁对象=staticLock)synchronized (staticLock) {staticCount++;System.out.printf("[%s] 静态变量count=%d%n", Thread.currentThread().getName(), staticCount);}}public static void main(String[] args) {SyncBlockDemo demo = new SyncBlockDemo();// 线程1和2更新实例变量(同一把锁,互斥)new Thread(demo::updateInstanceCount, "线程1").start();new Thread(demo::updateInstanceCount, "线程2").start();// 线程3和4更新静态变量(同一把锁,互斥)new Thread(demo::updateStaticCount, "线程3").start();new Thread(demo::updateStaticCount, "线程4").start();}
}
📌 实战避坑指南:
  • ❌ 不要用 “字符串常量” 作为锁对象(如 synchronized ("lock"))—— 字符串常量会被缓存,不同类的同步块可能共用同一把锁,导致意外互斥
  • ❌ 不要用 “可变对象” 作为锁对象(如 synchronized (new Object()) 或锁对象被重新赋值)—— 每次创建新对象会导致锁对象变化,锁失效
  • ✅ 推荐用法:用 private final Object lock = new Object(); 定义锁对象 ——final 保证锁对象不被修改,private 保证锁不被外部访问

3.3 用法 3:同步静态方法(锁对象 = 当前类的 Class 对象)

同步静态方法的锁对象是 “类对象”(如 SyncStaticMethodDemo.class),属于 “类级锁”—— 所有该类的实例共享同一把锁,即使是不同实例,调用同步静态方法也会互斥。

代码实现(带场景对比)
/*** 同步静态方法:锁对象=当前类的Class对象(如SyncStaticMethodDemo.class)* 特点:类级锁,所有实例共用同一把锁*/
public class SyncStaticMethodDemo {private static int staticCount = 0;// 同步静态方法:锁对象=SyncStaticMethodDemo.classpublic static synchronized void incrementStatic() {staticCount++;System.out.printf("[%s] 静态方法:count=%d%n", Thread.currentThread().getName(), staticCount);}// 普通静态方法(无锁)public static void commonStaticMethod() {System.out.printf("[%s] 普通静态方法执行%n", Thread.currentThread().getName());}public static void main(String[] args) {// 场景1:不同实例调用同步静态方法(共用类锁,互斥)SyncStaticMethodDemo demo1 = new SyncStaticMethodDemo();SyncStaticMethodDemo demo2 = new SyncStaticMethodDemo();new Thread(demo1::incrementStatic, "线程A").start();new Thread(demo2::incrementStatic, "线程B").start();// 结果:线程A和B互斥,因为锁对象都是SyncStaticMethodDemo.class// 场景2:调用普通静态方法(无锁,不互斥)new Thread(SyncStaticMethodDemo::commonStaticMethod, "线程C").start();new Thread(SyncStaticMethodDemo::commonStaticMethod, "线程D").start();}
}
📌 适用场景与注意:
  • 适合保护 “静态共享变量”(如统计全局请求数的静态变量)
  • 注意:同步静态方法与同步实例方法不会互斥 —— 因为锁对象不同(类对象 vs 实例对象)
  • 性能提示:类级锁的粒度较粗(所有实例共用),高并发场景下可能导致 “锁竞争激烈”,建议优先用同步代码块减小锁粒度

3.4 三种用法对比表(面试必背)

用法类型

锁对象

互斥范围

适用场景

同步实例方法

当前实例 this

同一实例的所有同步实例方法

保护实例级共享变量

同步代码块

自定义对象(如 lock)

仅同步代码块内的逻辑

灵活保护临界区,减小锁粒度

同步静态方法

类对象 Class

所有实例的同步静态方法

保护静态共享变量


四、生产者 - 消费者模型:线程安全与通信的经典实战

生产者 - 消费者模型是多线程协作的 “教科书级案例”,核心是通过 synchronized 保证线程安全,通过 wait()/notifyAll() 实现线程通信(生产者等待队列满,消费者等待队列空)。

4.1 基础实现(带完整注释与优化)

import java.util.LinkedList;
import java.util.Queue;/*** 生产者-消费者模型:基于synchronized + wait()/notifyAll()实现* 核心需求:* 1. 队列容量固定(5),生产者满时等待,消费者空时等待* 2. 保证线程安全(队列的添加/移除操作互斥)* 3. 线程通信(生产者唤醒消费者,消费者唤醒生产者)*/
public class ProducerConsumerOptimized {// 队列:存储生产的产品(共享资源)private final Queue<Integer> productQueue;// 队列最大容量private final int maxCapacity;// 产品编号(用于标识不同产品)private int productId = 0;// 构造方法:初始化队列和容量public ProducerConsumerOptimized(int maxCapacity) {this.productQueue = new LinkedList<>();this.maxCapacity = maxCapacity;}/*** 生产者方法:生产产品并加入队列*/public void produce() throws InterruptedException {// 同步代码块:锁对象=productQueue(保护队列操作)synchronized (productQueue) {// 注意:用while而非if判断队列状态(避免“虚假唤醒”)while (productQueue.size() == maxCapacity) {System.out.printf("[%s] 队列已满(容量%d),等待消费者消费...%n", Thread.currentThread().getName(), maxCapacity);// 等待并释放锁,直到被notifyAll()唤醒productQueue.wait();}// 生产产品(临界区操作)productId++;productQueue.add(productId);System.out.printf("[%s] 生产产品:%d,当前队列大小:%d%n", Thread.currentThread().getName(), productId, productQueue.size());// 唤醒所有等待的线程(包括消费者和其他生产者)productQueue.notifyAll();}}/*** 消费者方法:从队列中消费产品*/public void consume() throws InterruptedException {synchronized (productQueue) {// while循环判断:避免虚假唤醒(线程被唤醒后队列可能仍为空)while (productQueue.isEmpty()) {System.out.printf("[%s] 队列为空,等待生产者生产...%n", Thread.currentThread().getName());productQueue.wait();}// 消费产品(临界</doubaocanvas>

http://www.dtcms.com/a/581612.html

相关文章:

  • 【全网首发】2025 华为OD 机试双机位A卷机考真题库清单(全真题库)含考点说明(OD上机考试双机位A卷)
  • AI研究-123 FSD V14 深度解析:纯视觉 SDF 与端到端 相比 V12 具体升级
  • python做网站好处陕西网站建设哪家好
  • 网站建设推销话术用模块做网站
  • Rust 练习册 :Macros与宏系统
  • 【Ruby】Mixins扩展方式之include,extend和prepend
  • 欧美 电台 网站模板4防伪查询网站
  • MTK外包面经
  • [linux] grep命令的使用
  • 前后端跨域问题解决
  • 通往AGI的模块化路径:一个可能的技术架构(同时解答微调与RAG之争)
  • cartographer ros 配置详解
  • 告别人工登高 无人机智能巡检平台让效率提升300%
  • docker登录ghcr.io
  • 网站评估 源码wordpress 建立数据库连接时出错 用户名密码可能不正确
  • 划清界限:深度解读EUDR法案的适用范围,谁将受到冲击?
  • 数据结构初阶:Java中的Stack和Queue
  • Node.js环境变量配置的实战技术
  • 帮人做网站赚多少钱邯郸市内最新招聘信息
  • 提问:Flutter 项目在浏览器中运行失败是怎么回事?
  • Node.js 多进程
  • 基于spark岗位招聘推荐系统 基于用户协同过滤算法 Django框架 数据分析 可视化 大数据 (建议收藏)✅
  • 《Flutter全栈开发实战指南:从零到高级》- 12 -状态管理Bloc
  • 装饰工程东莞网站建设百度seo外包
  • CSS 提示工具:高效开发利器
  • IDE 开发的一天
  • Jwt令牌、过滤器、拦截器快速入门
  • 做画找图网站网站建设的公司合肥
  • h5支付宝支付 - 支付宝文档中心1.登录 支付宝开放平台 创建 网页/移动应用
  • Java八股—MySQL