現(xiàn)在翅溺,并發(fā)應(yīng)用程序最關(guān)鍵的方面之一是共享數(shù)據(jù)脑漫。當(dāng)你創(chuàng)建線程實現(xiàn)Runnable接口,然后開始各種線程對象使用相同的Runnable對象未巫,所有線程共享窿撬,Runnable對象內(nèi)部定義相同的屬性。這本質(zhì)上意味著叙凡,如果您更改了線程中的任何屬性,所有線程都將受到此更改的影響密末,并將通過第一個線程看到修改后的值握爷。有時它是你希望的行為,例如多個線程增加/減少相同的計數(shù)器變量严里;但有時您希望確保每個線程都必須工作在自己的線程實例副本上新啼,并且不影響其他數(shù)據(jù)。
ThreadLocal
每個線程的Thread對象中都有一個ThreadLocalMap對象刹碾,這個對象存儲來一組以ThreadLocal.threadLocalHashCode為鍵燥撞,以本地線程變量為值的K-V值對,ThreadLocal對象就是當(dāng)前線程的ThreadLocalMap的訪問入口迷帜,每一個ThreadLocal對象都包含獨一無二的threadLocalHashCode值物舒,使用這個值就可以在線程K-V值中終會對應(yīng)的本地線程變量。
何時使用ThreadLocal
比如戏锹,你正在從事電子商務(wù)應(yīng)用程序冠胯,需要為每個客戶請求生成一個唯一的交易ID,控制器進程需要將此交易ID傳遞給Manager / DAO類中的業(yè)務(wù)方法锦针,以便進行日志記錄荠察。一個解決方案可能是將此交易ID作為參數(shù)傳遞給所有業(yè)務(wù)方法。但這不是一個好的解決方案奈搜,因為代碼是多余的和不必要的悉盆。
為了解決這個問題,在這里你可以使用ThreadLocal變量馋吗。你可以在控制器或任何預(yù)處理器攔截交易ID焕盟,并設(shè)置此交易ID到ThreadLocal里。在這之后耗美,不管該控制器調(diào)用什么方法京髓,它們都可以從ThreadLocal訪問此交易ID航缀。請注意,應(yīng)用程序控制器將在一次服務(wù)多個請求堰怨,因為每個請求在單獨的線程中在框架級別處理芥玉,交易ID將是每個線程唯一的,并且將從線程的執(zhí)行路徑中訪問备图。
ThreadLocal API介紹
Java Concurrency API為ThreadLocal變量的使用提供良好的機制和優(yōu)秀的性能灿巧。
public class ThreadLocal<T> extends Object {...}
該類提供線程本地變量。這些變量與一般的變量不同揽涮,每個線程訪問一個線程(通過get或set方法)有自己獨立的變量初始化副本抠藕。ThreadLocal實例通常是私有的靜態(tài)字段在類希望關(guān)聯(lián)狀態(tài)的線程(例如,一個用戶ID或交易ID)
這個類有以下方法:
get():返回該線程局部變量的當(dāng)前線程的值復(fù)制蒋困。
initialvalue():返回該線程局部變量的當(dāng)前線程的“初始值”盾似。
remove():刪除該線程局部變量的當(dāng)前線程的值。
set(T value):將當(dāng)前線程的本地線程變量的副本設(shè)置為指定的值雪标。
如何使用ThreadLocal
下面的例子使用了兩個線程局部變量零院,即threadId和startDate。它們都被定義為建議的“私有靜態(tài)”字段村刨。threadId將被用來確定當(dāng)前正在運行的線程和startDate用來表示啟動線程的執(zhí)行的時間告抄。以上信息將打印在控制臺中,以驗證每個線程是否保留了自己的變量副本嵌牺。
public class DemoTask implements Runnable {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static int getThreadId() {
return threadId.get();
}
private static final ThreadLocal<Date> startDate = new ThreadLocal<Date>() {
@Override
protected Date initialValue() {
return new Date();
}
};
@Override
public void run() {
System.out.printf("Starting Thread: %s : %s\n", getThreadId(), startDate.get());
try {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Thread Finished: %s : %s\n", getThreadId(), startDate.get());
}
public static void main(String[] args) {
DemoTask demoTask = new DemoTask();
Thread thread1 = new Thread(demoTask);
Thread thread2 = new Thread(demoTask);
Thread thread3 = new Thread(demoTask);
thread1.start();
thread2.start();
thread3.start();
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
現(xiàn)在來驗證變量基本上能夠保持自己的狀態(tài)打洼,不論多初始化為多少線程。我們創(chuàng)建了該任務(wù)的三個實例逆粹;啟動線程募疮;然后驗證信息在控制臺打印它們。
Starting Thread: 0 : Sat Aug 12 15:38:30 CST 2017
Starting Thread: 2 : Sat Aug 12 15:38:30 CST 2017
Starting Thread: 1 : Sat Aug 12 15:38:30 CST 2017
Thread Finished: 0 : Sat Aug 12 15:38:30 CST 2017
Thread Finished: 2 : Sat Aug 12 15:38:30 CST 2017
Thread Finished: 1 : Sat Aug 12 15:38:30 CST 2017
在上面的輸出中枯饿,打印語句的順序每次都會不同酝锅。我們可以清楚地看到ThreadLocal值為每個線程實例所保管。
最常見的ThreadLocal使用是當(dāng)您有一些對象不是線程安全的奢方,但您希望避免使用同步關(guān)鍵字/塊同步訪問該對象搔扁。相反,給每個線程自己的對象實例來工作蟋字。
一個很好的替代synchronization(同步)或ThreadLocal是使用局部變量稿蹲。局部變量始終是線程安全的。唯一阻止你這樣做的是應(yīng)用程序設(shè)計約束鹊奖。
在webapp服務(wù)器苛聘,它可能保持一個線程池,所以一個ThreadLocal變量應(yīng)該在響應(yīng)客戶端請求前刪除,因為當(dāng)前線程可以被下一個請求重復(fù)使用设哗。另外唱捣,如果你當(dāng)你完成請求不清理的時候,任何引用它加載的類將保持在永久堆作為部署webapp的一部分网梢,并永遠不會被垃圾回收震缭。
InheritableThreadLocal
InheritableThreadLocal類是ThreadLocal的子類。為了解決ThreadLocal實例內(nèi)部每個線程都只能看到自己的私有值战虏,所以InheritableThreadLocal允許一個線程創(chuàng)建的所有子線程訪問其父線程的值拣宰。
參考
1.Java ThreadLocal Variables – When and How to Use?
2.Java TheadLocal