ThreadLocal<T>其實是與線程綁定的一個變量。ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問黄痪。但是ThreadLocal與synchronized有本質(zhì)的區(qū)別钢猛。Synchronized用于線程間的數(shù)據(jù)共享缅叠,而ThreadLocal則用于線程間的數(shù)據(jù)隔離逆航。Synchronized是利用鎖的機制穴豫,使變量或代碼塊在某一時該只能被一個線程訪問怀大。而ThreadLocal為每一個線程都提供了變量的副本纱兑,使得每個線程在某一時間訪問到的并不是同一個對象,這樣就隔離了多個線程對數(shù)據(jù)的數(shù)據(jù)共享化借。而Synchronized卻正好相反潜慎,它用于在多個線程間通信時能夠獲得數(shù)據(jù)共享。
一句話理解ThreadLocal蓖康,向ThreadLocal里面存東西就是向它里面的Map存東西的铐炫,然后ThreadLocal把這個Map掛到當(dāng)前的線程底下,這樣Map就只屬于這個線程了蒜焊。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
// 通過靜態(tài)內(nèi)部類實現(xiàn)變量與線程綁定
static class ThreadLocalMap {...}
ThreadLocal其實是與線程綁定的一個變量倒信,如此就會出現(xiàn)一個問題:如果沒有將ThreadLocal內(nèi)的變量刪除(remove)或替換,它的生命周期將會與線程共存泳梆。通常線程池中對線程管理都是采用線程復(fù)用的方法鳖悠,在線程池中線程很難結(jié)束甚至于永遠(yuǎn)不會結(jié)束,這將意味著線程持續(xù)的時間將不可預(yù)測优妙,甚至與JVM的生命周期一致乘综。舉個例字,如果ThreadLocal中直接或間接包裝了集合類或復(fù)雜對象鳞溉,每次在同一個ThreadLocal中取出對象后瘾带,再對內(nèi)容做操作鼠哥,那么內(nèi)部的集合類和復(fù)雜對象所占用的空間可能會開始持續(xù)膨脹熟菲。
Spring使用ThreadLocal解決線程安全問題看政。通常只有無狀態(tài)的Bean才可以在多線程環(huán)境下共享,在Spring中抄罕,絕大部分Bean都可以聲明為singleton作用域允蚣。就是因為Spring對一些Bean(如RequestContextHolder、TransactionSynchronizationManager呆贿、LocaleContextHolder等)中非線程安全的“狀態(tài)性對象”采用ThreadLocal進行封裝嚷兔,讓它們也成為線程安全的“狀態(tài)性對象”,因此有狀態(tài)的Bean就能夠以singleton的方式在多線程中正常工作了做入。一般的Web應(yīng)用劃分為控制層冒晰、服務(wù)層和持久層三個層次,在不同的層中編寫對應(yīng)的邏輯竟块,下層通過接口向上層開放功能調(diào)用壶运。在一般情況下,從接收請求到返回響應(yīng)所經(jīng)過的所有程序調(diào)用都同屬于一個線程浪秘。這樣用戶就可以根據(jù)需要蒋情,將一些非線程安全的變量以ThreadLocal存放,在同一次請求響應(yīng)的調(diào)用線程中耸携,所有對象所訪問的同一ThreadLocal變量都是當(dāng)前線程所綁定的棵癣。
// 非線程安全
public class TopicDao {
//①一個非線程安全的變量
private Connection conn;
public void addTopic(){
//②引用非線程安全變量
Statement stat = conn.createStatement();
…
}
}
由于①處的conn是成員變量,因為addTopic()方法是非線程安全的夺衍,必須在使用時創(chuàng)建一個新TopicDao實例(非singleton)狈谊。下面使用ThreadLocal對conn這個非線程安全的“狀態(tài)”進行改造:
import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
//①使用ThreadLocal保存Connection變量
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
public static Connection getConnection(){
//②如果connThreadLocal沒有本線程對應(yīng)的Connection創(chuàng)建一個新的Connection,
//并將其保存到線程本地變量中沟沙。
if (connThreadLocal.get() == null) {
Connection conn = ConnectionManager.getConnection();
connThreadLocal.set(conn);
return conn;
}else{
//③直接返回線程本地變量
return connThreadLocal.get();
}
}
public void addTopic() {
//④從ThreadLocal中獲取線程對應(yīng)的
Statement stat = getConnection().createStatement();
}
}
不同的線程在使用TopicDao時的畴,先判斷connThreadLocal.get()是否為null,如果為null尝胆,則說明當(dāng)前線程還沒有對應(yīng)的Connection對象丧裁,這時創(chuàng)建一個Connection對象并添加到本地線程變量中;如果不為null含衔,則說明當(dāng)前的線程已經(jīng)擁有了Connection對象盹憎,直接使用就可以了。這樣乾颁,就保證了不同的線程使用線程相關(guān)的Connection棚品,而不會使用其他線程的Connection。因此杭隙,這個TopicDao就可以做到singleton共享了哟绊。 當(dāng)然,這個例子本身很粗糙痰憎,將Connection的ThreadLocal直接放在Dao只能做到本Dao的多個方法共享Connection時不發(fā)生線程安全問題票髓,但無法和其他Dao共用同一個Connection攀涵,要做到同一事務(wù)多Dao共享同一個Connection,必須在一個共同的外部類使用ThreadLocal保存Connection洽沟。但這個實例基本上說明了Spring對有狀態(tài)類線程安全化的解決思路以故。