线程间的通信
一、线程间通信核心概念线程间通信是多线程编程的核心难点之一其本质是协调多个线程的执行顺序实现数据共享和逻辑同步。Java 中实现线程通信的核心机制是等待 - 唤醒模型主要通过以下两种方式实现synchronized Object.wait()/notify()/notifyAll()基于原生同步锁的通信方式Lock Condition.await()/signal()/signalAll()基于 JUC 的更灵活的通信方式二、基础案例线程交替操作变量2.1 需求说明两个线程操作一个初始值为 0 的变量一个线程对变量 1一个线程对变量 - 1交替执行 10 轮。2.2 初始实现存在隐患class ShareData { private Integer number 0; /** * 变量1操作 */ public synchronized void increment() throws InterruptedException { // 判断不满足条件则等待 if (number ! 0) { this.wait(); } // 干活执行核心逻辑 number; System.out.println(Thread.currentThread().getName() : number); // 通知唤醒其他等待线程 this.notifyAll(); } /** * 变量-1操作 */ public synchronized void decrement() throws InterruptedException { // 判断 if (number ! 1) { this.wait(); } // 干活 number--; System.out.println(Thread.currentThread().getName() : number); // 通知 this.notifyAll(); } } public class NotifyWaitDemo { public static void main(String[] args) { ShareData shareData new ShareData(); // 1线程 new Thread(() - { for (int i 0; i 10; i) { try { shareData.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, AAA).start(); // -1线程 new Thread(() - { for (int i 0; i 10; i) { try { shareData.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, BBB).start(); } }2.3 虚假唤醒问题当线程数量扩展到 4 个2 个 1 线程2 个 - 1 线程时上述代码会出现虚假唤醒问题导致变量值错乱如出现 2、3 甚至负数。问题根源wait () 方法被唤醒后会从等待处继续执行而不是重新判断条件if 判断只会执行一次无法处理多次唤醒的场景解决方案使用 while 代替 if// 修正后的判断逻辑 while (number ! 0) { this.wait(); } while (number ! 1) { this.wait(); }2.4 使用 Condition 实现通信对标synchronizedsynchronized是原生的类jdk当中本身就自带的。所以可以用wait和notify来进行等待和唤醒。但是lock是juc当中的有自己的一套等待和唤醒的方法。Condition 是 Lock 锁的配套通信工具相比 Object 的 wait/notify提供了更灵活的线程控制能力class ShareData { private Integer number 0; // 创建可重入锁 private final Lock lock new ReentrantLock(); // 创建Condition对象 private final Condition condition lock.newCondition(); /** * 变量1操作 */ public void increment() throws InterruptedException { lock.lock(); // 加锁 try { // 判断防止虚假唤醒 while (number ! 0) { condition.await(); // 替代wait() } // 干活 number; System.out.println(Thread.currentThread().getName() : number); // 通知 condition.signalAll(); // 替代notifyAll() } finally { lock.unlock(); // 解锁 } } /** * 变量-1操作 */ public void decrement() throws InterruptedException { lock.lock(); try { while (number ! 1) { condition.await(); } number--; System.out.println(Thread.currentThread().getName() : number); condition.signalAll(); } finally { lock.unlock(); } } }三、进阶实战定制化线程通信3.1 需求说明实现三个线程按顺序执行AA 线程打印 5 次BB 线程打印 10 次CC 线程打印 15 次循环执行 10 轮3.2 实现思路使用 ReentrantLock 保证线程安全为每个线程创建独立的 Condition 对象通过标志位控制线程执行顺序执行完成后修改标志位并唤醒下一个线程3.3 完整代码import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ThreadOrderAccess { // 全局锁对象 private final Lock lock new ReentrantLock(); // 为每个线程创建独立的Condition private final Condition conditionA lock.newCondition(); private final Condition conditionB lock.newCondition(); private final Condition conditionC lock.newCondition(); // 执行标志位1-A执行 2-B执行 3-C执行 private int flag 1; /** * A线程打印5次 */ public void print5() { lock.lock(); try { // 判断不是A线程执行时机则等待 while (flag ! 1) { conditionA.await(); } // 干活打印5次 for (int i 1; i 5; i) { System.out.println(Thread.currentThread().getName() , i); } // 修改标志位唤醒B线程 flag 2; conditionB.signalAll(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } } /** * B线程打印10次 */ public void print10() { lock.lock(); try { while (flag ! 2) { conditionB.await(); } for (int i 1; i 10; i) { System.out.println(Thread.currentThread().getName() , i); } flag 3; conditionC.signalAll(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } } /** * C线程打印15次 */ public void print15() { lock.lock(); try { while (flag ! 3) { conditionC.await(); } for (int i 1; i 15; i) { System.out.println(Thread.currentThread().getName() , i); } // 重置标志位唤醒A线程开始下一轮 flag 1; conditionA.signalAll(); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } } public static void main(String[] args) { ThreadOrderAccess access new ThreadOrderAccess(); // 启动A线程 new Thread(() - { for (int i 0; i 10; i) { access.print5(); } }, AA).start(); // 启动B线程 new Thread(() - { for (int i 0; i 10; i) { access.print10(); } }, BB).start(); // 启动C线程 new Thread(() - { for (int i 0; i 10; i) { access.print15(); } }, CC).start(); } }四、面试实战12A34B...5152Z4.1 需求分析线程 1打印 1-52 的数字线程 2打印 A-Z 的字母执行顺序12A34B...5152Z4.2 实现代码import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class NumberLetterPrint { private final Lock lock new ReentrantLock(); private final Condition numCondition lock.newCondition(); private final Condition letterCondition lock.newCondition(); // 控制标志位true-数字线程执行 false-字母线程执行 private boolean flag true; // 数字计数器 private int num 1; // 字母计数器 private char letter A; /** * 打印数字的方法 */ public void printNumber() { lock.lock(); try { while (num 52) { // 不是数字执行时机则等待 while (!flag) { numCondition.await(); } // 打印两个数字 System.out.print(num); System.out.print(num 1); num 2; // 切换标志位唤醒字母线程 flag false; letterCondition.signal(); // 数字打印完直接退出避免等待 if (num 52) { break; } } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } } /** * 打印字母的方法 */ public void printLetter() { lock.lock(); try { while (letter Z) { // 不是字母执行时机则等待 while (flag) { letterCondition.await(); } // 打印一个字母 System.out.print(letter); letter; // 切换标志位唤醒数字线程 flag true; numCondition.signal(); } } catch (InterruptedException e) { throw new RuntimeException(e); } finally { lock.unlock(); } } public static void main(String[] args) { NumberLetterPrint print new NumberLetterPrint(); // 数字线程 new Thread(print::printNumber, 数字线程).start(); // 字母线程 new Thread(print::printLetter, 字母线程).start(); } }五、核心总结5.1 多线程编程模板线程操作资源类将共享数据和操作封装到独立的资源类中高内聚低耦合判断 - 干活 - 通知在资源类方法中遵循 判断条件→执行逻辑→通知其他线程 的流程防止虚假唤醒使用 while 循环替代 if 判断等待条件5.2 关键注意事项wait/await 必须在同步块中使用否则会抛出 IllegalMonitorStateException虚假唤醒多线程场景下必须使用 while 循环重新检查条件Condition 优势一个 Lock 可以创建多个 Condition实现精准的线程唤醒锁的释放finally 块中必须释放锁避免死锁5.3 技术选型建议简单场景使用 synchronized wait/notify复杂场景需要精准唤醒使用 Lock Condition高并发场景考虑使用 CountDownLatch、CyclicBarrier 等工具类通过以上案例和分析我们可以看到线程间通信的核心是通过等待 - 唤醒机制协调线程执行顺序结合标志位控制可以实现复杂的线程调度需求。在实际开发中合理选择通信方式并遵循编程模板能够有效避免多线程问题。