即使只有一个入口,也会在内部为每个线程分配特有的存储空间。
在Java标准类库中,java.lang.ThreadLocal类实现了该模式。
关于java.lang.ThreadLocal类
ThreadLocal的实例会管理多个对象
由于一个ThreadLocal的实例可以管理多个对象,所以ThreadLocal定义了可以“存储”(set)和“获取”(get)对象的方法。
set方法
ThreadLocal类的set方法用于将通过参数接收的实例与调用该方法的线程(当前线程)对应并存储起来。这里存储的对象可以通过get方法获取。set方法中没有表示线程的参数。set方法会先查询当前线程(即表达式Thread.currentThread()的值),然后以它作为键来存储实例。
get方法
ThreadLocal类的get方法用于获取与调用get方法的线程(当前线程)对应的实例。该线程之前通过set方法存储的实例就是get方法的返回值。如果之前一次都还没有调用过set方法,则get方法的返回值为null。
与set方法一样,get方法中也没有表示线程的参数。这是因为,get方法也会去查询当前线程。即get方法会以当前线程自身作为健去获取对象。
java.lang.ThreadLocal与泛型
java.lang.ThreadLocal是一个泛型类,可以通过参数的类型来指定要存储的对象的类型。ThreadLocal类的声明大致如下:
1 | public class ThreadLocal<T> { |
即,通过ThreadLocal
示例1:不使用Thread-Specific Storage模式
使用Log类的静态方法将日志记录在文件中。
类图
Log.java
1 | import java.io.FileWriter; |
Main.java
1 | public class Main { |
运行结果
1 | BEGIN |
Timethreads图
示例2:使用了Thread-Specific Storage模式
制作一种程序结构,在其中编写一个继承了Thread的类,但是并不在它的字段中保存PrintWriter的实例,而只是机械地在方法中调用Log.println。仅仅这样就可以实现自动地将字符串分配到各个线程的日志文件中。
类图
TSLog.java
1 | import java.io.FileWriter; |
Log.java
1 | public class Log { |
ClientThread.java
1 | public class ClientThread extends Thread { |
Main.java
1 | public class Main { |
运行结果
1 | Bobby BEGIN |
Timethreads图
登场角色
Client(委托者)
Client角色将处理委托给TSObjectProxy角色。一个TSObjectProxy角色会被多个Client角色使用。
在示例程序中,由ClientThread类扮演此角色。
TSObjectProxy(线程特有的对象的代理人)
TSObjectProxy角色会执行多个Client角色委托给它的处理。
首先,TSObjectProxy角色使用TSObjectCollection角色获取与Client角色对应的TSObject角色。接着,它将处理委托给TSObject角色。
在示例程序中,由Log类扮演此角色。
TSObjectCollection(线程特有的对象的集合)
TSObjectCollection角色有一张Client角色与TSObject角色之间的对应表。
当getTSObject方法被调用后,它会去查看对应表,返回与Client角色相对应的TSObject角色。另外,当setTSObject方法被调用后,它会将Client角色与TSObject角色之间的键值对应关系设置到对应表中。
在示例程序中,由java.lang.ThreadLocal类扮演此角色。
TSObject(线程特有的对象)
TSObject角色中保存着线程特有的信息。
TSObject角色由TSObjectCollection角色管理。TSObject角色的方法只会被单线程调用。
在示例程序中,由TSLog类扮演此角色。
类图
时序图
新创建TSObject角色
多个Client角色访问各自的TSObject角色