一、定義
Thread-Specific Storage就是“線程獨有的存儲庫”,該模式會對每個線程提供獨有的記憶體空間。
java.lang.ThreadLocal
類提供了該模式的實作,ThreadLocal的執行個體是一種集合(collection)架構,該執行個體管理了很多對象,可以想象成一個保管有大量保險箱的房間。
java.lang.ThreadLocal類的方法:
- public void set()
該方法會檢查目前調用線程,預設以該線程的
Thread.currentThread()
值作為鍵,來儲存指定的值。
- public Object get()
該方法會檢查目前調用線程,預設以該線程的
Thread.currentThread()
值作為鍵,擷取儲存指定的值。
二、模式案例
TSLog類:
//實際執行記錄日志的類,每個線程都會擁有一個該類的執行個體
public class TSLog {
private PrintWriter writer = null;
public TSLog(String filename) {
try {
writer = new PrintWriter(new FileWriter(filename));
} catch (IOException e) {
e.printStackTrace();
}
}
public void println(String s) {
writer.println(s);
}
public void close() {
writer.println("==== End of log ====");
writer.close();
}
}
Log類:
public class Log {
private static final ThreadLocal<TSLog> tsLogCollection = new ThreadLocal<TSLog>();
public static void println(String s) {
getTSLog().println(s);
}
public static void close() {
getTSLog().close();
}
private static TSLog getTSLog() {
TSLog tsLog = (TSLog) tsLogCollection.get();
if (tsLog == null) {
tsLog = new TSLog(Thread.currentThread().getName() + "-log.txt");
tsLogCollection.set(tsLog);
}
return tsLog;
}
}
ClientThread類:
public class ClientThread extends Thread {
public ClientThread(String name) {
super(name);
}
public void run() {
System.out.println(getName() + " BEGIN");
for (int i = 0; i < 10; i++) {
Log.println("i = " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
Log.close();
System.out.println(getName() + " END");
}
}
執行:
Alice、Bobby、Chris三個線程調用Log類的同一個方法,但實際上每個線程都擁有獨自的TSLog執行個體。
public class Main {
public static void main(String[] args) {
new ClientThread("Alice").start();
new ClientThread("Bobby").start();
new ClientThread("Chris").start();
}
}
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM0ITMvw1dvwlMvwlM3VWaWV2Zh1WaDdTJwlmc0N3LcRnbllmcv1yb0VXYvwlMyd2bNV2Zh1Wa-cmbw5SZyQTNhdzNxImNhNWZmNWMtIDOxIjN0ATMvw1cldWYtl2XkF2bsBXdvw1bp5SdoNnbhlmauMXZnFWbp1CZh9GbwV3Lc9CX6MHc0RHaiojIsJye.png)
三、模式講解
Thread-Specific Storage模式的角色如下:
- Client(委托人)參與者
Client參與者會将工作委托給TSObjectProxy參與者。(案例中的ClientThread類就是Client)
- TSObjectProxy(線程獨有對象的代理者)參與者
TSObjectProxy參與者會處理多個Client委托的工作。(案例中的Log類就是TSObjectProxy)
- TSObjectCollection(線程獨有對象的集合)參與者
(案例中的
java.lang.ThreadLocal
類就是TSObjectCollection)
- TSObject(線程獨有的對象)參與者
TSObject存放線程所特有的資訊,TSObject執行個體的方法隻會由單線程調用,由TSObjectCollection管理,每個線程都擁有獨立的TSObject執行個體。(案例中的TSLog類就是TSObject)
四、ThreadLocal的原理
ThreadLocal類主要有四個方法:
1、初始化傳回值的方法:
該方法實作隻傳回 null,并且修飾符為protected,很明顯,如果使用者想傳回初始值不為null,則需要重寫該方法;
protected T initialValue() {
return null;
}
2、get方法,擷取線程本地副本變量
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
3、set方法,設定線程本地副本變量
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
4、remove方法,移除線程本地副本變量
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
如果需要我們自己來設計ThreadLocal對象,那麼,一般的實作思路:
設計一個線程安全的Map,key就是目前線程對象,Value就是線程本地變量的值。
然而,JDK的實作思路:
讓每個Thread對象,自身持有一個Map,這個Map的Key就是目前ThreadLocal對象,Value是本地線程變量值。相對于加鎖的實作方式,這樣做可以提升性能,其實是一種以時間換空間的思路。
ThreadLocal類有個
getMap()
方法,其實就是傳回Thread對象自身的Map——threadLocals。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
threadLocals是一種
ThreadLocal.ThreadLocalMap
類型的資料結構,作為内部類定義在ThreadLocal類中,其内部采用一種WeakReference的方式儲存鍵值對。