Java 并发工具、设计模式与 GUI 事件
上一篇解决了”线程怎么抢”,本篇解决”线程怎么合作”:wait/notify 通信、Lock 锁、线程池是并发进阶的三件套;再配上单例模式的线程安全演进史和两个常用设计模式,入门到中级之间的桥就搭好了。
本篇目标
读完本篇你应该能够:用 wait/notify 写出正确的生产者消费者模型;说出 Lock 相对 synchronized 的改进点;解释为什么需要线程池并用 Executors 提交任务;写出饿汉式、懒汉式、双重检查锁三种单例并讲清它们的线程安全性;理解简单工厂、工厂方法、适配器三种模式各解决什么问题。
线程通信:wait 与 notify
synchronized 保证的是互斥,但线程之间还需要协作。经典场景是生产者消费者:生产者往缓冲区放数据,满了就等;消费者取数据,空了就等;双方互相唤醒。
class Buffer { |
三个硬规则必须记牢:
- wait/notify/notifyAll 必须在同步块内,由锁对象调用。这三个方法属于 Object 而非 Thread,因为它们是”锁对象上的等待队列管理”,脱离锁调用会抛
IllegalMonitorStateException。 - wait 会释放锁,sleep 不会。这是两者最本质的区别,也是高频面试题。
- 判断条件用 while 不用 if。notify 随机唤醒一个等待线程,如果唤醒的是同类线程(比如已有数据时又唤醒一个生产者),if 只判断一次就会出错;while 让线程醒来后重新检查条件。多线程通信场景一律
while + notifyAll搭配。
Lock 与 Condition:更灵活的锁
JDK 5 引入 ReentrantLock,把锁从语法变成对象:
private final ReentrantLock lock = new ReentrantLock(); |
相比 synchronized 的三点改进:lock/unlock 手动控制,可以跨方法、可以 tryLock 带超时避免死等(呼应上篇的死锁预防);一把锁可挂多个 Condition,生产者等 notFull、消费者等 notEmpty,signal 时精准唤醒对方,解决了 notifyAll 无差别唤醒的浪费;可中断、可公平,排队策略可选。代价是必须手动 unlock,忘记释放就是永久死锁——所以 finally 块是铁律。
线程池:为什么不能再”每任务一线程”
直接 new Thread 有两个硬伤:创建贵——每个线程都要向操作系统申请资源、分配栈空间(默认 1MB 左右);不可控——并发量一高,线程数暴涨,CPU 全耗在上下文切换上,最终 OOM。线程池的思路是”一次创建、反复复用”:线程执行完任务不销毁,回到池子里等下一个任务,把创建销毁的成本摊平。
JDK 5 起内置支持:
ExecutorService pool = Executors.newFixedThreadPool(2); |
这是创建线程的第三种方式:Runnable 无返回值,Callable 有返回值还能抛异常。需要提醒一个时代化细节:Executors 的工厂方法虽然方便,但 newFixedThreadPool 用的是无界队列(任务堆积会 OOM),newCachedThreadPool 允许近乎无上限的线程数,生产环境建议手动 new ThreadPoolExecutor,显式指定核心线程数、最大线程数、队列容量和拒绝策略,把风险暴露在参数里而不是藏在默认值里。
单例模式:一场线程安全的演进
单例保证一个类在内存中只有一个实例:私有构造 + 私有静态实例 + 公开静态获取方法。
饿汉式——类加载时就创建:
class Singleton { |
天生线程安全(类加载由 JVM 保证只发生一次),缺点是用不用都先创建。
懒汉式——用到才创建,但并发下有坑:
public static Singleton getInstance() { |
两个线程同时进入 if,就会创建两个实例。加 synchronized 能修,但每次获取都加锁太重。
双重检查锁(DCL)——演进终点:
class Singleton { |
new Singleton() 不是原子操作,包含分配内存、初始化、赋值三步,指令重排序可能让”赋值”先于”初始化”完成,其他线程就会拿到半初始化对象——volatile 禁止重排序,正是为此而加。这段代码把前面所有并发知识串在了一起,值得逐行吃透。JDK 里 Runtime.getRuntime() 就是饿汉式单例的活例子。
工厂与适配器:两个入门设计模式
简单工厂:一个静态方法按参数创建对象,客户端不再 new 具体类。缺点是新增产品要改工厂类,违反开闭原则。
工厂方法:为每种产品配一个工厂类,工厂实现统一接口。新增产品只需加一对”产品类 + 工厂类”,不改旧代码——用加类的代价换掉改类的风险,这是设计模式”以扩展代替修改”的典型思路。
适配器:让不兼容的接口协同工作。Java GUI 里的 WindowAdapter 就是活例:WindowListener 接口有七个方法,只想处理关闭事件时继承适配器重写一个方法即可,其余空实现由适配器兜底。
GUI 事件监听机制
以 AWT 为例,事件模型三要素:事件源(被操作的组件,如窗口)、事件对象(封装这次操作的细节)、监听器(实现了监听接口的对象,注册到事件源上)。用户点关闭按钮,JVM 自动回调监听器里的方法:
Frame frame = new Frame("我的窗口"); |
这套”注册—回调”机制就是观察者模式的落地:事件源维护一份监听器列表,事件发生时逐个回调,自己完全不关心监听器做了什么,双方因此解耦。它也是日后理解 Spring 事件、消息队列监听、前端 addEventListener 的思想原型。GUI 本身已被 JavaFX 和前端技术取代,但事件驱动思想长青——学它不是为了写桌面程序,而是为了领会这种”反转控制权”的编程范式。
小结
- wait/notify 三铁律:锁对象调用、wait 释放锁、while 判条件;多线程通信用 notifyAll。
- Lock + Condition 提供更细粒度的锁与精准唤醒,unlock 必须放 finally。
- 线程池用复用换创建成本;Executors 快速上手,生产环境手动 new ThreadPoolExecutor。
- 单例演进线是”饿汉安全但浪费 → 懒汉省但漏 → DCL + volatile 两全”,它是并发知识的综合考题。
- 工厂模式以扩展代修改,适配器弥合接口差异,GUI 事件监听则是观察者模式的启蒙。

