目录一、线程安全基础1. 线程状态2. 线程安全问题成因3. 原子性解决方案锁synchronized二、synchronized 基本用法与原理1. 非线程安全的计数器问题代码2. 加锁解决线程安全问题synchronized 代码块锁的核心逻辑解释3. synchronized 的其他写法1修饰实例方法锁对象为 this2修饰静态方法锁对象为类对象 XXX.class4. 可重入锁synchronized 的特性5. 死锁问题与哲学家就餐模型哲学家就餐问题经典死锁场景死锁条件代码示例模拟死锁风险两个线程两把锁死锁分析三、总结一、线程安全基础1. 线程状态线程的基本状态包括NEW,TERMINATED,RUNNABLE,WAITING,TIMED_WAITING,BLOCKED。2. 线程安全问题成因多个线程同时修改同一个变量。操作不是原子性如count实际是load→add→save三步。指令重排序此处暂不展开。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加锁成功→ 执行load→add→save→unlock解锁。t2执行lock时因锁被t1占用加锁失败→ 放弃 CPU进入阻塞状态。t1解锁后t2再次尝试lock加锁成功→ 继续执行。3. synchronized 的其他写法1修饰实例方法锁对象为thisclass 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 个哲学家围坐圆桌每人需左右两根筷子锁才能吃饭。若所有哲学家同时拿起左手筷子再尝试拿右手筷子时会因右手筷子被邻座占用而阻塞→ 永远等待死锁。死锁条件互斥资源筷子同一时间只能被一个线程哲学家占用。占有且等待线程持有资源的同时等待其他资源。不可剥夺资源不能被强制剥夺哲学家不会放下已拿的筷子。循环等待存在资源依赖的循环链如哲学家 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。此时t1等locker2t2等locker1→循环等待占有且等待→ 死锁。三、总结synchronized是 Java 提供的原子性可见性有序性的解决方案基于对象锁实现。锁的粒度要小只保护必要的代码段避免性能问题。可重入锁避免了“递归加锁”导致的死锁。死锁需避免破坏死锁的四个条件如“一次性申请所有资源”“允许资源抢占”“按序申请资源”等。
8.Java多线程之synchronized深度解析(含代码+死锁分析)
目录一、线程安全基础1. 线程状态2. 线程安全问题成因3. 原子性解决方案锁synchronized二、synchronized 基本用法与原理1. 非线程安全的计数器问题代码2. 加锁解决线程安全问题synchronized 代码块锁的核心逻辑解释3. synchronized 的其他写法1修饰实例方法锁对象为 this2修饰静态方法锁对象为类对象 XXX.class4. 可重入锁synchronized 的特性5. 死锁问题与哲学家就餐模型哲学家就餐问题经典死锁场景死锁条件代码示例模拟死锁风险两个线程两把锁死锁分析三、总结一、线程安全基础1. 线程状态线程的基本状态包括NEW,TERMINATED,RUNNABLE,WAITING,TIMED_WAITING,BLOCKED。2. 线程安全问题成因多个线程同时修改同一个变量。操作不是原子性如count实际是load→add→save三步。指令重排序此处暂不展开。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加锁成功→ 执行load→add→save→unlock解锁。t2执行lock时因锁被t1占用加锁失败→ 放弃 CPU进入阻塞状态。t1解锁后t2再次尝试lock加锁成功→ 继续执行。3. synchronized 的其他写法1修饰实例方法锁对象为thisclass 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 个哲学家围坐圆桌每人需左右两根筷子锁才能吃饭。若所有哲学家同时拿起左手筷子再尝试拿右手筷子时会因右手筷子被邻座占用而阻塞→ 永远等待死锁。死锁条件互斥资源筷子同一时间只能被一个线程哲学家占用。占有且等待线程持有资源的同时等待其他资源。不可剥夺资源不能被强制剥夺哲学家不会放下已拿的筷子。循环等待存在资源依赖的循环链如哲学家 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。此时t1等locker2t2等locker1→循环等待占有且等待→ 死锁。三、总结synchronized是 Java 提供的原子性可见性有序性的解决方案基于对象锁实现。锁的粒度要小只保护必要的代码段避免性能问题。可重入锁避免了“递归加锁”导致的死锁。死锁需避免破坏死锁的四个条件如“一次性申请所有资源”“允许资源抢占”“按序申请资源”等。