读书笔记 - Java 多线程编程实战指南
第十章 Thread Specific Storage(线程特有存储)模式
模式介绍
- 多线程相关的问题如线程安全、死锁等归根究底是多线程共享变量导致的。有鉴于此,Thread Specific Storage 模式通过不共享变量实现了线程安全,并由此自然地避免了与锁的消耗及与之有关的问题。
- Java 1.2 引入的标准类库 java.lang.ThreadLocal 就相当于访问线程特有变量的逻辑统一入口点。
-
模式架构
-
实战案例
某系统需要支付验证码短信功能。该系统的用户进行一些重要操作的时候,该系统会生成一个验证码,并将其通过短信发送给用户。验证码是一个 6 位数的随机数字。这里,为了提高安全性,验证码的生成需要使用 java.security.SecureRandom 这种强随机数生成器,而非 java.math.Random 这种伪随机数生成器。但是,使用 SecureRandom 可能会涉及以下几个问题。
- SecureRandom 实例的初始化(主要是初始化种子)可能比较耗时间。这点在 JVM 的宿主机作系统为 Linux 系统时更为明显。这与 SecureRandom 的内部实现有关。因此,我们希望能够复用 SecureRandom 实例,而不是每次需要生成一个验证码的时候就生成一个 SecureRandom 实例。
- SecureRandom 用于生成随机数的 nextInt 方法最终会调用一个由 SecureRandom 自身定义的 synchronized 方法。这意味着,nextInt 方法的调用实际上会涉及锁。因此,如果多个线程共用同一个 SecureRandom 实例,那么当一个线程正在调用该实例的 nextInt 方法生成随机数的时候,其他线程只能等待。但是,我们不希望看到这种等待:由于验证码生成后需要通过短信发送给用户,而该系统下发短信给用户涉及网络 I/O 这种相对慢的操作,我们希望验证码的生成能够尽量快,从而不耽误短信的下发。
尽管 SecureRandom 是线程安全的,但是综合分析上述两个问题,我们决定不在多个线程间共享 SecureRandom 实例,并且也不每次生成验证码的时候就创建一个 SecureRandom。这时,我们可以借用 Thread Specific Storage 模式:让每个需要生成验证码的线程生成一个且仅一个 SecureRandom 实例。
本案例 Demo 详见 multithreading 工程的 com.ThreadPool 包
-
Thread Specific Storage 模式的评价与现实考量
- Thread Specific Storage 模式提升了计算效率。Thread Specific Storage 模式使得我们可以在不使用锁的情况下实现线程安全,从而避免了锁的开销以及由锁带来的相关问题,如上下文切换、死锁等。
- Thread Specific Storage 模式易于使用。Thread Specific Storage 模式通过引入 TSObjectProxy 这个参与者隐藏了其相关实现细节,客户端代码只需要与 TSObjectProxy 参与者实例打交道即可获取所需的线程特有对象。
- Thread Specific Storage 模式的常见使用场景包括以下几个:
- 需要使用非线程安全对象,但又不希望引入锁。
- 使用线程安全对象,但希望避免其使用的锁的开销和相关问题。
- 隐式参数传递。
- 特定于线程单例(Singleton)模式:广为使用的单例模式所实现的是,对于一个 JVM 中的一个类加载器而言,某个类有且仅有一个实例。如果对于某个类,希望每个线程有且仅有该类的一个实例,那么就可以使用 Thread Specific Storage 模式。Thread Specific Storage 模式中,同一个应用线程多次调用同一个 TSObjectProxy 实例所得到的线程特有对象实例都是同一个实例(只要该线程没用调用 TSObjectProxy 实例的 setThreadSpecific 方法替换过线程特有对象实例)。
-
Java 标准库实例
JDK 1.7 中引入的标准库类 java.util.concurrent.ThreadLocalRandom 就使用了 Thread Specific Storage 模式。ThreadLocalRandom 是 ThreadLocal 的一个子类,其 current 方法返回一个属于当前线程的 ThreadLocalRandom 实例。这里,current 方法的返回值就是一个线程特有对象实例。因此,不同线程调用 ThreadLocalRandom 实例的相关方法获取下一个随机数的时候,相互之间不影响。这比多个线程共享一个 java.math.Random 实例来获取随机数效率要高,尽管 Random 类也是线程安全的。