Wetts's blog

Stay Hungry, Stay Foolish.

0%

Java多线程编程实战指南-第10章-Thread Specific Storage(线程特有存储)模式

读书笔记 - Java 多线程编程实战指南


第十章 Thread Specific Storage(线程特有存储)模式

模式介绍

  • 多线程相关的问题如线程安全、死锁等归根究底是多线程共享变量导致的。有鉴于此,Thread Specific Storage 模式通过不共享变量实现了线程安全,并由此自然地避免了与锁的消耗及与之有关的问题。
  • Java 1.2 引入的标准类库 java.lang.ThreadLocal 就相当于访问线程特有变量的逻辑统一入口点。

-

模式架构

-

实战案例

某系统需要支付验证码短信功能。该系统的用户进行一些重要操作的时候,该系统会生成一个验证码,并将其通过短信发送给用户。验证码是一个 6 位数的随机数字。这里,为了提高安全性,验证码的生成需要使用 java.security.SecureRandom 这种强随机数生成器,而非 java.math.Random 这种伪随机数生成器。但是,使用 SecureRandom 可能会涉及以下几个问题。

  1. SecureRandom 实例的初始化(主要是初始化种子)可能比较耗时间。这点在 JVM 的宿主机作系统为 Linux 系统时更为明显。这与 SecureRandom 的内部实现有关。因此,我们希望能够复用 SecureRandom 实例,而不是每次需要生成一个验证码的时候就生成一个 SecureRandom 实例。
  2. 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 模式的常见使用场景包括以下几个:
  1. 需要使用非线程安全对象,但又不希望引入锁。
  2. 使用线程安全对象,但希望避免其使用的锁的开销和相关问题。
  3. 隐式参数传递。
  4. 特定于线程单例(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 类也是线程安全的。