防止任務在共享資源上產生衝突的一種方式是根除對變量的共享,使用線程的本地存儲爲使用相同變量的不一樣線程建立不一樣的存儲。java
下面是一個 ThreadLocal 的實例。這裏咱們使用了靜態的全局變量 ThreadLocal
對象來保存 Integer
類型的值。咱們在不一樣的線程中將指定的數字傳入到 threadLocal
中進行保存。而後,再將其讀取出來:數據庫
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public static void main(String...args) {
threadLocal.set(-1);
ExecutorService executor = Executors.newCachedThreadPool();
for (int i=0; i<5; i++) {
final int ii = i; // i不能是final的,建立臨時變量
executor.submit(new Runnable() {
public void run() {
threadLocal.set(ii);
System.out.println(threadLocal.get());
}
});
}
executor.shutdown();
System.out.println(threadLocal.get());
}
複製代碼
從程序的執行結果能夠看出,每一個線程都正確地讀取出來了保存到 ThreadLocal 中的數據。數組
因此,咱們總結一下 ThreadLocal
的做用就是,存儲在 ThreadLocal
中的變量是線程安全的,每一個線程只能讀取出本身存儲的值。安全
一般它的使用方式就是定義一個靜態全局的 ThreadLocal
實例,而後每一個線程使用它來讀寫只有本身會用到的數據。好比,咱們要爲每一個線程建立了一個數據庫鏈接,而且該鏈接只容許該線程本身使用,那麼能夠將它存儲在 ThreadLocal
中,而後在用到的地方獲取。數據結構
看了上面的例子,也許你會又如下一些問題:this
帶着上面的這些問題,咱們來看下在JDK源碼中 ThreadLocal
是如何實現的。spa
咱們仍是先從讀取的操做來看。線程
如下是 ThreadLocal
中 set()
方法的代碼:code
public T get() {
Thread t = Thread.currentThread(); // 1
ThreadLocalMap map = getMap(t); // 2
if (map != null) { // 3
ThreadLocalMap.Entry e = map.getEntry(this); // 4
if (e != null) {
T result = (T) e.value; // 5
return result;
}
}
return setInitialValue();
}
複製代碼
這裏首先會再步驟1中獲取到當前線程的實例,而後在步驟2中經過getMap()
方法,使用當前的線程的ThreadLocalMap
。這裏的ThreadLocalMap
的定義以下:cdn
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
// ...
}
複製代碼
而後,咱們看下getMap()
方法的定義:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
複製代碼
也就是說實際上當咱們調用 get()
方法的時候,會先獲取當前線程中的 threadLocals
字段,該字段是 ThreadLocalMap
類型的。而後,咱們使用當前的 ThreadLocal
實例做爲鍵來從哈希表中獲取到一個 Entry
,而實際的值就保存再 Entry
的 value
字段中。
就像上面的 getEntry()
方法定義的那樣,彷佛這裏的哈希表只是一個數組,那哈希衝突是怎麼解決的呢?實際上,咱們知道一般解決哈希衝突有兩種解決方式,一種是拉鍊法,一種是線性探測法。前者在 HashMap
和 ConcurrentHashMap
中使用較多,而這裏用到的其實就是線性探測法。說白了就是將全部的值放在一個數組裏面而後根據散列的結果到數組中取值,具體的實現方式能夠看相關的數據結構知識點。
這裏的關係是否是有點亂,咱們來捋一下:
咱們使用ThreadLocal
存儲的值實際是存儲在Thread
使用ThreadLocalMap
當中的,而這裏的ThreadLocal
實例值起到了一個哈希表的鍵的做用:
就像上圖顯示的那樣,假如咱們在線程thread1
中調用了threadLocal1
的get()
方法,首先會用Thread.currentThread()
方法獲取到thread1
,而後獲取到thread1
的threadLocals
實例,threadLocals
是一個ThreadLocalMap
類型的哈希表。而後,咱們再用threadLocal1
做爲鍵來從threadLocals
中獲取到值Entry
,並從Entry
中取出存儲的值並返回。
至此,咱們已經瞭解了ThreadLocal的實現的原理,原本想看下set()
方法的,可是到此已經基本真相大白了,因此也就沒有繼續下去的必要了。
咱們回過頭來看下以前提出的幾個問題:
以上就是ThreadLocal的用法和實現原理。