synchronized這類線程同步的機制可以解決多線程并發(fā)問題,在這種解決方案下,多個線程訪問到的,都是同一份變量的內(nèi)容。為了防止在多線程訪問的過程中,可能會出現(xiàn)的并發(fā)錯誤。不得不對多個線程的訪問進(jìn)行同步,這樣也就意味著,多個線程必須先后對變量的值進(jìn)行訪問或者修改,這是一種以延長訪問時間來換取線程安全性的策略。
而ThreadLocal類為每一個線程都維護(hù)了自己獨有的變量拷貝。每個線程都擁有了自己獨立的一個變量,競爭條件被徹底消除了,那就沒有任何必要對這些線程進(jìn)行同步,它們也能最大限度的由CPU調(diào)度,并發(fā)執(zhí)行。并且由于每個線程在訪問該變量時,讀取和修改的,都是自己獨有的那一份變量拷貝,變量被徹底封閉在每個訪問的線程中,并發(fā)錯誤出現(xiàn)的可能也完全消除了。對比前一種方案,這是一種以空間來換取線程安全性的策略。
用ThreadLocal來實現(xiàn)數(shù)據(jù)庫連接Connection對象線程隔離的例子。
public class ConnectionManager {
privatestatic ThreadLocal connectionHolder = new ThreadLocal() {
@Override
protected Connection initialValue() {
Connection conn = null;
try {
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "username",
"password");
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
};
publicstatic Connection getConnection() {
return connectionHolder.get();
}
publicstatic void setConnection(Connection conn) {
connectionHolder.set(conn);
}
}
通過調(diào)用ConnectionManager.getConnection()方法,每個線程獲取到的,都是和當(dāng)前線程綁定的那個Connection對象,第一次獲取時,是通過initialValue()方法的返回值來設(shè)置值的。通過ConnectionManager.setConnection(Connectionconn)方法設(shè)置的Connection對象,也只會和當(dāng)前線程綁定。這樣就實現(xiàn)了Connection對象在多個線程中的完全隔離。在Spring容器中管理多線程環(huán)境下的Connection對象時,采用的思路和以上代碼非常相似。
ThreadLocal類是如何實現(xiàn)這種“為每個線程提供不同的變量拷貝”的呢?先來看一下ThreadLocal的set()方法的源碼是如何實現(xiàn)的:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
首先通過getMap(Threadt)方法獲取一個和當(dāng)前線程相關(guān)的ThreadLocalMap,然后將變量的值設(shè)置到這個ThreadLocalMap對象中,當(dāng)然如果獲取到的ThreadLocalMap對象為空,就通過createMap方法創(chuàng)建。
線程隔離的秘密,就在于ThreadLocalMap這個類。ThreadLocalMap是ThreadLocal類的一個靜態(tài)內(nèi)部類,它實現(xiàn)了鍵值對的設(shè)置和獲?。▽Ρ萂ap對象來理解),每個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當(dāng)前線程讀取和修改。ThreadLocal類通過操作每一個線程特有的ThreadLocalMap副本,從而實現(xiàn)了變量訪問在不同線程中的隔離。因為每個線程的變量都是自己特有的,完全不會有并發(fā)錯誤。還有一點就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設(shè)置的對象了。
為了加深理解,我們接著看上面代碼中出現(xiàn)的getMap和createMap方法的實現(xiàn):
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
代碼已經(jīng)說的非常直白,就是獲取和設(shè)置Thread內(nèi)的一個叫threadLocals的變量,而這個變量的類型就是ThreadLocalMap,這樣進(jìn)一步驗證了上文中的觀點:每個線程都有自己獨立的ThreadLocalMap對象。打開java.lang.Thread類的源代碼,我們能得到更直觀的證明:
ThreadLocal.ThreadLocalMap threadLocals = null;
再看一下ThreadLocal類中的g et()方法,代碼是這么說的:
public Tget() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
private TsetInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
在獲取和當(dāng)前線程綁定的值時,ThreadLocalMap對象是以this指向的ThreadLocal對象為鍵進(jìn)行查找的,這當(dāng)然和前面set()方法的代碼是相呼應(yīng)的。
我們可以創(chuàng)建不同的ThreadLocal實例來實現(xiàn)多個變量在不同線程間的訪問隔離,因為不同的ThreadLocal對象作為不同鍵,當(dāng)然也可以在線程的ThreadLocalMap對象中設(shè)置不同的值了。通過ThreadLocal對象,在多線程中共享一個值和多個值的區(qū)別,就像你在一個HashMap對象中存儲一個鍵值對和多個鍵值對一樣。
設(shè)置到這些線程中的隔離變量,會不會導(dǎo)致內(nèi)存泄漏呢?ThreadLocalMap對象保存在Thread對象中,當(dāng)某個線程終止后,存儲在其中的線程隔離的變量,也將作為Thread實例的垃圾被回收掉,所以完全不用擔(dān)心內(nèi)存泄漏的問題。在多個線程中隔離的變量,光榮的生,合理的死,真是圓滿,不是么?
最后再提一句,ThreadLocal變量的這種隔離策略,也不是任何情況下都能使用的。如果多個線程并發(fā)訪問的對象實例只允許,也只能創(chuàng)建那么一個,那就沒有別的辦法了,老老實實的使用同步機制來訪問吧。
愛華網(wǎng)


