文章目录
11.1 Thread-Specific Storage模式
- 有一个储物间,里面并排摆放着许多储物柜。一个人拿着自己的钥匙进入了储物间,出来时手上拿着自己的行李。别人也拿着自己的钥匙进入了储物间。但是,虽然进人的是同一个储物间,打开的当然是另外一个储物柜。使用者都会从各自的储物柜中取出自己的行李。
- Specific是 “特定的” 的意思,Storage是储存柜、存储装置的意思。因此,所谓 Thread-Specific Storage 就是 “每个线程特有的存储柜”、“为每个线程准备的存储空间” 的意思。
- Thread-Specific Storage模式是一种即使只有一个入口,也会在内部为每个线程分配特有的存储空间的模式。
11.2 java.lang.ThreadLocal
11.2.1 java.lang.ThreadLocal 就是储物间
- 将 Java.lang.ThreadLocal 的实例当作一种集合可能会有助于大家理解它。也就是说,一个ThreadLocal的实例会管理多个对象。
- ThreadLocal 类的 set 方法用于将通过参数接收的实例与调用该方法的线程(当前线程)对应并存储起来。这里存储的对象可以通过get方法获取。set方法中没有表示线程的参数。set方法会先查询当前线程(即表达式Thread.currentThread()的值),然后以它作为键来存储实例。
- ThreadLocal 类的 get 方法用于获取与调用 get 方法的线程(当前线程)对应的实例。该线程之前通过 set 方法存储的实例就是 get 方法的返回值。如果之前一次都还没有调用过 set 方法,则 get方法的返回值为null。
11.2.2 java.lang.ThreadLocal 与泛型
- java.lang.ThreadLocal是一个泛型类,可以通过参数的类型来指定要存储的对象的类型。ThreadLocal 类的声明大致如下:
public class ThreadLocal <T>
{
//存储
public void set (T value)
{
...
}
//获取
public T get ()
{
...
}
...
}
- 即通过
ThreadLocal<T>
的 T 指定的类型就是 set 方法的参数的类型以及 get 方法的返回值的类型。
11.5 Thread-Specific Storage模式中的角色
11.5.1 Client(委托者)
- Client 角色将处理委托给 TSObjectProxy 角色。一个 TSObjectProxy 角色会被多个 Client 角色使用。
11.5.2 TSObjectProxy(线程特有的对象的代理人)
- TSObjectProxy 角色会执行多个Client角色委托给它的处理。
- 首先,TSObjectProxy 角色使用 TSObjectCollection 角色获取与 Client 角色对应的TSObject 角色。接着,它将处理委托给TSObject角色。
11.5.3 TSObjectCollection(线程特有的对象的集合)
- TSObjectCollection 角色有一张 Client 角色与 TSObject 角色之间的对应表。当
getTSObject()
方法被调用后,它会去查看对应表,返回与 Client 角色相对应的 TSObject 角色。另外,当setTSObject()
方法被调用后,它会将 Client 角色与 TSObject 角色之间的键值对应关系设置到对应表中。 - 在示例程序中,由 Java.lang.ThreadLocal 类扮演此角色。
11.5.4 TSObject(线程特有的对象)
- TSObject 角色中保存着线程特有的信息。
- TSObject 角色由 TSObjectCollection 角色管理。TSObject角色的方法只会被单线程调用。
11.5.5 类图与时序图
11.6 拓展思路的要点
11.6.1 局部变量与 java.lang.ThreadLocal 类
- 线程本来都是有自己特有的存储空间的,即用于保存方法的局部变量的栈。方法中定义的局部变量属于该线程特有,其他线程无法访问它们。但是,这些变量在方法调用结束后就会消失。而 ThreadLocal 则与方法调用无关,它是一个用于为线程分配特有的存储空间的类。
11.6.2 保存线程特有的信息的位置
- 线程特有的信息的 “保存位置” 有以下两种:
- 线程外(thread-external)
- 线程内(thread-internal)
11.6.2.1 在线程外保存线程特有的信息
- 示例程序中的线程特有的信息是 TSLog 的实例,而所有的 TSLog 的实例都被保存在 Log 类中的Java.lang.ThreadLocal 的实例中。
- ThreadLocal 的实例就是储物间,各个线程的储物柜都被集中在这个储物间内。线程并不会背着储物柜四处走动。像这样,将线程特有的信息保存在线程外部的方法称为 “线程外”。
- 将线程特有的信息保存在线程外部的方法不需要修改既有的表示线程的类,所以可以适用于任何线程。但是,随之而来的是表示线程的类的代码可能会变得难以理解。这是因为,仅仅查看表示线程的类的源代码是无法知道其他类中是否还保存着线程特有的信息的。
11.6.2.2 在线程内保存线程特有的信息
- 假设我们编写了一个Thread类的子类 MyThread。如果在MyThread中声明字段,该字段就是线程特有的信息。这就是在线程内保存线程特有的信息。采用这种方法时,通过阅读线程的源代码可以很容易地知道线程有哪些特有的信息。但是,随之而来的是当以后需要增加线程特有的信息时,必须修改MyThread类。
- 我们使用 “有” 这个词的时候,有时候表示的是 “拥有” 的意思,有时候也表示实际地 “在手上” 的意思。
- 例如 “有钱” 这个词并不一定表示钱就确实地 “在手中” 。这是因为,有可能钱被存放在了诸如银行这样的TSObjectCollection角色里面。
11.6.2.3 总结
- 在线程外部保存线程特有的信息的方法:虽然有钱,但是钱不在手中
- 在线程内部保存线程特有的信息的方法:钱确实地在自己手中
11.6.3 不必担心其他线程访问
- Thread-Specific Storage 模式为我们提供了一种以线程作为键,让每个线程只能访问它特有的对象的机制。
- 允许被多个线程访问的是 TSObjectProxy 角色。接着,TSObjectProxy角色(使用TSObjectCollection角色)将 TSObject 角色分配给了各个线程。在Thread-Specific Storage模式中,在多个线程之间被共享的部分只到TSObjectProxy角色为止,实际的处理是在将 TSObject 角色分配给每个线程之后才执行的。
11.6.4 吞吐量的提高很大程序上取决于实现方式
- Thread-Specific Storage 模式并没有执行互斥处理。因此,这很容易让人误解为与使用Single Threaded Execution模式相比,此时的吞吐量会有所提高。
- 但是事实上可能 TSObjectCollection 角色中执行了隐藏的互斥处理。此外,每次通过 TSObjectProxy 角色调用方法时,使用 TSObjectCollection 角色来获取 TSObject 角色都会产生额外的性能开销。
- 与强调吞吐量相比,Thread-Specific Storage 模式更看重如下所示的可复用性:
- 不改变结构即可实现程序
- 没有显式地执行互斥处理,所以编程时犯错的可能性较小
11.8 基于角色与基于任务
11.8.1 主体与客体
- 要想组装塑料模型,以下二者缺一不可:
- 组装塑料模型的人
- 塑料模型套件(说明书和零件)
只有组装塑料模型的人或是只有塑料模型套件时,无法完成塑料模型。
-
同理,假设我们需要让线程去完成一项工作,那么以下二者缺一不可:
- 进行工作的线程
- 进行工作所需的信息
-
无论是组装塑料模型,还是让线程进行工作,以下二者缺一不可:
- 用于做某事的主体
- 用于做某事的客体
-
在设计多线程程序时,根据以 “主体” 为主还是以 “客体” 为主的不同产生了以下两种方式:
- 基于角色:以主体为主
- 基于任务:以客体为主
11.8.2 基于角色的考虑方式
- 所谓基于角色,一言以蔽之即 “线程最伟大” 的方式。
- 基于角色的方式即在表示线程的实例中保存进行工作所必需的信息(上下文、状态)。这样可以减少和减轻线程之间的交互信息量。一个线程会使用从其他线程接收到的信息来执行处理,改变自己的内部状态。通常,我们称这样的线程为角色。
- 假设我们要编写一段程序,在这段程序中定义了一个Thread类的子类,然后在子类中定义了一个字段,并在这个字段中保存与工作相关的信息。这样,我们就完成了一个 “小角色”:
class Actor extends Thread\
{
角色的内部状态
public void run ()
{
循环地从外部接收并执行任务,改变内部状态
}
}
11.8.3 基于任务的考虑方式
- 所谓基于任务,一言以蔽之即 “任务最伟大” 的方式。
- 基于任务的方式不在线程中保存信息(上下文、状态)。在这种方式下,这些信息不保存在线程中,而是保存在线程之间交互的实例中。而且,不仅是数据,连用于执行请求的方法都定义在其中。像这样在线程之间交互的实例可以称为消息、请求或是命令。这里我们暂且称其为任务。
- 由于任务中保存了足够的信息,所以任何线程执行该任务都没有问题。可以说,这是一种富(rich)任务往来于轻线程之间的方式。
- 使用该方式的一个典型的模式是Worker Thread模式。
- 我们为带有信息的类加上 implements Runnable,然后实现run方法后,线程就可以执行该类了。这样就编写完成了一个 “小任务”。将编写完成的任务传递给线程后,线程就会进行工作:
class Task implements Runnable
{
进行工作所必须的信息
public void run ()
{
工作的处理内容
}
}