SQL學習:四
ThreadLocal翻譯成中文比較準確的叫法應該是:執行緒區域性變數。
這個玩意有什麼用處,或者說為什麼要有這麼一個東東?先解釋一下,在併發程式設計的時候,成員變數如果不做任何處理其實是執行緒不安全的,各個執行緒都在操作同一個變數,顯然是不行的,並且我們也知道volatile這個關鍵字也是不能保證執行緒安全的。那麼在有一種情況之下,我們需要滿足這樣一個條件:變數是同一個,但是每個執行緒都使用同一個初始值,也就是使用同一個變數的一個新的副本。這種情況之下ThreadLocal就非常使用,比如說DAO的資料庫連線,我們知道DAO是單例的,那麼他的屬性Connection就不是一個執行緒安全的變數。而我們每個執行緒都需要使用他,並且各自使用各自的。這種情況,ThreadLocal就比較好的解決了這個問題。
我們從原始碼的角度來分析這個問題。
首先定義一個ThreadLocal:
public final class ConnectionUtil { private ConnectionUtil() {} private static final ThreadLocal<Connection> conn = new ThreadLocal<>(); public static Connection getConn() { Connection con = conn.get(); if (con == null) { try { Class.forName("com.mysql.jdbc.Driver"); con = DriverManager.getConnection("url", "userName", "password"); conn.set(con); } catch (ClassNotFoundException | SQLException e) { // ... } } return con; } }
這樣子,都是用同一個連線,但是每個連線都是新的,是同一個連線的副本。
那麼實現機制是如何的呢?
1、每個Thread物件內部都維護了一個ThreadLocalMap這樣一個ThreadLocal的Map,可以存放若干個ThreadLocal。
1 2 3 |
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null ;
|
2、當我們在呼叫get()方法的時候,先獲取當前執行緒,然後獲取到當前執行緒的ThreadLocalMap物件,如果非空,那麼取出ThreadLocal的value,否則進行初始化,初始化就是將initialValue的值set到ThreadLocal中。
1 2 3 4 5 6 7 8 9 10 |
public T get() {
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();
}
|
3、當我們呼叫set()方法的時候,很常規,就是將值設定進ThreadLocal中。
4、總結:當我們呼叫get方法的時候,其實每個當前執行緒中都有一個ThreadLocal。每次獲取或者設定都是對該ThreadLocal進行的操作,是與其他執行緒分開的。
5、應用場景:當很多執行緒需要多次使用同一個物件,並且需要該物件具有相同初始化值的時候最適合使用ThreadLocal。
6、其實說再多也不如看一下原始碼來得清晰。如果要看原始碼,其中涉及到一個WeakReference和一個Map,這兩個地方需要了解下,這兩個東西分別是a.Java的弱引用,也就是GC的時候會銷燬該引用所包裹(引用)的物件,這個threadLocal作為key可能被銷燬,但是隻要我們定義成他的類不解除安裝,tl這個強引用就始終引用著這個ThreadLocal的,永遠不會被gc掉。b.和HashMap差不多。
事實上,從本質來講,就是每個執行緒都維護了一個map,而這個map的key就是threadLocal,而值就是我們set的那個值,每次執行緒在get的時候,都從自己的變數中取值,既然從自己的變數中取值,那肯定就不存線上程安全問題,總體來講,ThreadLocal這個變數的狀態根本沒有發生變化,他僅僅是充當一個key的角色,另外提供給每一個執行緒一個初始值。如果允許的話,我們自己就能實現一個這樣的功能,只不過恰好JDK就已經幫我們做了這個事情。