8.Java多线程之synchronized深度解析(含代码+死锁分析)
2026/6/6 6:13:56 网站建设 项目流程

目录

一、线程安全基础

1. 线程状态

2. 线程安全问题成因

3. 原子性解决方案:锁(synchronized)

二、synchronized 基本用法与原理

1. 非线程安全的计数器(问题代码)

2. 加锁解决线程安全问题(synchronized 代码块)

锁的核心逻辑(解释):

3. synchronized 的其他写法

(1)修饰实例方法(锁对象为 this)

(2)修饰静态方法(锁对象为类对象 XXX.class)

4. 可重入锁(synchronized 的特性)

5. 死锁问题与哲学家就餐模型

哲学家就餐问题(经典死锁场景):

死锁条件:

代码示例:模拟死锁风险(两个线程,两把锁)

死锁分析:

三、总结


一、线程安全基础

1. 线程状态

线程的基本状态包括:NEW,TERMINATED,RUNNABLE,WAITING,TIMED_WAITING,BLOCKED

2. 线程安全问题成因

  • 多个线程同时修改同一个变量

  • 操作不是原子性(如count++实际是loadaddsave三步)。

  • 指令重排序(此处暂不展开)。

3. 原子性解决方案:锁(synchronized)

操作系统提供“锁”机制,Java 通过synchronized关键字实现。

锁的核心特性:互斥(同一时间只有一个线程持有锁)共享(锁释放后其他线程可竞争)

竞争锁失败会导致线程进入BLOCKED状态(锁竞争/冲突)。

二、synchronized 基本用法与原理

1. 非线程安全的计数器(问题代码)

以下代码中,两个线程同时对count自增 5 万次,最终结果不一定是 10 万(因count++非原子操作)。

package thread; public class Demo14 { private static int count = 0; // 3 usages public static void main(String[] args) throws InterruptedException { // 创建两个线程,分别对一个变量进行 5w 次的 ++ 操作。 // 最终主线程打印结果。 Thread t1 = new Thread(() -> { for (int i = 0; i < 50000; i++) { count++; } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 50000; i++) { count++; } }); t1.start(); t2.start(); // 让主线程等待,等待上述两个线程结束 t1.join(); t2.join(); System.out.println("count = " + count); } }

2. 加锁解决线程安全问题(synchronized 代码块)

通过synchronized代码块对count++加锁,保证原子性。

package thread; public class Demo14 { private static int count = 0; // 3 usages private static Object locker = new Object(); // 2 usages public static void main(String[] args) throws InterruptedException { // 创建两个线程,分别对一个变量进行 5w 次的 ++ 操作。 // 最终主线程打印结果。 Thread t1 = new Thread(() -> { for (int i = 0; i < 50000; i++) { synchronized (locker) { count++; } } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 50000; i++) { synchronized (locker) { count++; } } }); t1.start(); t2.start(); // 让主线程等待,等待上述两个线程结束 t1.join(); t2.join(); System.out.println("count = " + count); } }
锁的核心逻辑(解释):
  • t1先执行lock(加锁成功)→ 执行loadaddsaveunlock(解锁)。

  • t2执行lock时,因锁被t1占用,加锁失败→ 放弃 CPU,进入阻塞状态。

  • t1解锁后,t2再次尝试lock,加锁成功→ 继续执行。

3. synchronized 的其他写法

(1)修饰实例方法(锁对象为this

class Counter { public int count = 0; // 1 usage public void add() { // no usages synchronized (this) { count++; } } } public class Demo15 { public static void main(String[] args) throws InterruptedException { Counter c = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 50000; i++) { c.add(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 50000; i++) { c.add(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(c.count); } }

也可简化为直接修饰实例方法(等价于synchronized(this)):

class Counter { public int count = 0; // 1 usage synchronized public void add() { // 2 usages count++; } }

(2)修饰静态方法(锁对象为类对象XXX.class

静态方法的锁是类对象(如Demo16.class),而非实例对象。

package thread; public class Demo16 { private static int count = 0; // 2 usages private static void add() { // 2 usages synchronized (Demo16.class) { count++; } } public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { for (int i = 0; i < 50000; i++) { add(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 50000; i++) { add(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); } }

也可简化为直接修饰静态方法(等价于synchronized(Demo16.class)):

private synchronized static void add() { // 2 usages count++; }

4. 可重入锁(synchronized 的特性)

synchronized可重入锁:同一线程多次加锁不会死锁,锁会记录“持有线程”,解锁时逐层释放。

示例:

void func1() { synchronized (locker) { func2(); } } void func2() { synchronized (locker) { // 同一线程再次加锁,直接成功(可重入) } }

若锁不可重入,上述代码会死锁(外层加锁后,内层再申请锁会被阻塞)。

5. 死锁问题与哲学家就餐模型

哲学家就餐问题(经典死锁场景):
  • 5 个哲学家围坐圆桌,每人需左右两根筷子(锁)才能吃饭。

  • 若所有哲学家同时拿起左手筷子,再尝试拿右手筷子时,会因右手筷子被邻座占用而阻塞→ 永远等待(死锁)。

死锁条件:
  1. 互斥:资源(筷子)同一时间只能被一个线程(哲学家)占用。

  2. 占有且等待:线程持有资源的同时,等待其他资源。

  3. 不可剥夺:资源不能被强制剥夺(哲学家不会放下已拿的筷子)。

  4. 循环等待:存在资源依赖的循环链(如哲学家 A 等 B 的筷子,B 等 C 的… 形成环)。

代码示例:模拟死锁风险(两个线程,两把锁)
public class Demo18 { private static void sleep(long millis) { // no usages try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } } public static void main(String[] args) { Object locker1 = new Object(); Object locker2 = new Object(); Thread t1 = new Thread(() -> { synchronized (locker1) { System.out.println("t1 获取到 locker1"); sleep(1000); // 模拟思考/吃饭时间 synchronized (locker2) { System.out.println("t1 获取到 locker2"); } } }); Thread t2 = new Thread(() -> { synchronized (locker2) { System.out.println("t2 获取到 locker2"); sleep(1000); synchronized (locker1) { System.out.println("t2 获取到 locker1"); } } }); t1.start(); t2.start(); } }
死锁分析:
  • t1先拿到locker1,休眠后尝试拿locker2

  • t2先拿到locker2,休眠后尝试拿locker1

  • 此时t1locker2t2locker1循环等待+占有且等待→ 死锁。

三、总结

  1. synchronized是 Java 提供的原子性+可见性+有序性的解决方案,基于对象锁实现。

  2. 锁的粒度要小(只保护必要的代码段),避免性能问题。

  3. 可重入锁避免了“递归加锁”导致的死锁。

  4. 死锁需避免:破坏死锁的四个条件(如“一次性申请所有资源”“允许资源抢占”“按序申请资源”等)。

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

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

立即咨询