Java 的多线程编程提供了强大的工具,例如 wait() 和 notify() 方法,用于线程间的协调。然而,这些方法必须在同步块中调用,否则会抛出 IllegalMonitorStateException 异常。此外,ThreadLocal 提供了一种简单的方式为每个线程存储独立的数据,但若使用不当,可能导致内存泄漏问题。
多线程编程一直是软件开发中极具挑战性的领域。如何在多个线程之间协调工作?为什么某些方法必须在特定上下文中使用?为什么一个看似简单的工具如 ThreadLocal,可能隐藏着难以发现的内存泄漏风险?

1. 为什么 wait() 和 notify() 必须在同步块中调用?
1.1 wait() 和 notify() 的作用
wait() 和 notify() 是 Object 类中的方法,用于线程间的通信。wait() 会使当前线程进入等待状态,直到被其他线程唤醒;而 notify() 则负责唤醒一个等待中的线程。
代码:
输出可能为:
1.2 必须在同步块中调用的原因
调用 wait() 和 notify() 时,线程需要持有目标对象的监视器(即锁)。这是因为:
- 线程安全:多线程环境下,多个线程可能同时访问同一个对象,若没有锁机制,
wait()和notify()的调用顺序可能被打乱,导致不可预期的行为。 - 确保原子性:通过同步块或同步方法,Java 保证了这些方法的调用和锁的释放/获取是原子的。
如果在非同步块中调用,会抛出以下异常:
1.3 在非同步块中调用
以下代码会导致异常:
2. ThreadLocal 的内存泄漏问题
2.1 什么是 ThreadLocal?
ThreadLocal 提供了一种机制,用于为每个线程保存独立的变量副本。其典型使用场景包括:
- 数据库连接管理
- 用户会话存储
- 线程上下文变量
代码:
输出:
2.2 内存泄漏的原因
ThreadLocal 的潜在问题来自其实现机制。每个线程维护一个 ThreadLocalMap,键为 ThreadLocal 对象,值为具体存储的数据。如果 ThreadLocal 对象没有被及时清理,可能导致以下问题:
- 强引用泄漏:
ThreadLocalMap使用的是弱引用,但其值为强引用,如果线程长时间存在,值对象无法被回收。 - 线程池问题:在线程池中,线程会被重复利用,导致旧数据可能长期存在。
2.3 导致内存泄漏的代码
如果线程长时间不终止或线程池重复使用该线程,内存可能无法释放。
3. 避免 ThreadLocal 内存泄漏的最佳实践
3.1 使用 remove() 方法
显式调用 remove() 方法清理数据:
3.2 避免存储大对象
尽量避免使用 ThreadLocal 存储大体积对象,如缓存或文件数据。
3.3 使用框架自带的工具
某些框架提供了更安全的上下文变量管理工具,例如 Spring 的 RequestContextHolder。
总结
Java 的 wait() 和 notify() 方法以及 ThreadLocal 的设计初衷是为开发者提供强大的多线程工具,但它们的使用需要小心。同步块是 wait() 和 notify() 正常工作的基础,确保线程间通信的安全性;而 ThreadLocal 虽然简化了线程内数据管理,但不当的使用可能导致严重的内存泄漏。通过掌握这些机制和最佳实践,可以避免常见的陷阱,提高代码的健壮性和效率。

被折叠的 条评论
为什么被折叠?



