1.簡述ThreadLocalhtml
ThreadLocal實例一般做爲靜態的私有的(private static)字段出如今一個類中,這個類用來關聯一個線程。ThreadLocal是一個線程級別的局部變量,下面是線程局部變量(ThreadLocal variables)的關鍵點:數組
A、當使用ThreadLocal維護變量時,若多個線程訪問ThreadLocal實例,ThreadLocal爲每一個使用該變量的線程提供了一個獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其餘線程所對應的副本。安全
B、從線程的角度看,目標變量就像是線程的本地變量,這也是類名中Local所要表達的意思。併發
2.細看ThreadLocalide
ThreadLocal<T>類很簡單,只有四個方法:函數
(1)void set(T value),該方法用來設置當前線程中變量的副本高併發
(2)public T get(),該方法是用來獲取ThreadLocal在當前線程中保存的變量副本ui
(3)public void remove(),該方法用來移除當前線程中變量的副本,目的是爲了減小內存的佔用,該方法是JDK 5.0新增的方法。須要指出的是,當線程結束之後,對應線程的局部變量將自動被垃圾回收,因此顯式調用該方法清除線程的局部變量並非必須的操做,但它能夠加快內存回收的速度。this
(4)protected T initialValue(),該方法是一個protected方法,通常是用來在使用時進行重寫的,它是一個延遲加載方法,ThreadLocal中的缺省實現直接返回一個null。spa
3.ThreadLocal示例
簡單的使用方法以下:
package com.test; public class ThreadMain { // ①經過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值 private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() { public Integer initialValue() { return 0; } }; // ②獲取下一個序列值 public int getNextNum() { seqNum.set(seqNum.get() + 1); return seqNum.get(); } public static void main(String[] args) { ThreadMain sn = new ThreadMain(); // ③ 3個線程共享sn,各自產生序列號 TestClient t1 = new TestClient(sn); TestClient t2 = new TestClient(sn); TestClient t3 = new TestClient(sn); t1.start(); t2.start(); t3.start(); } private static class TestClient extends Thread { private ThreadMain sn; public TestClient(ThreadMain sn) { this.sn = sn; } public void run() { for (int i = 0; i < 3; i++) { // ④每一個線程打出3個序列值 System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn[" + sn.getNextNum() + "]"); } } } }
結果以下:
thread[Thread-0] --> sn[1] thread[Thread-2] --> sn[1] thread[Thread-1] --> sn[1] thread[Thread-2] --> sn[2] thread[Thread-0] --> sn[2] thread[Thread-2] --> sn[3] thread[Thread-1] --> sn[2] thread[Thread-1] --> sn[3] thread[Thread-0] --> sn[3]
另外一個案例
package com.csu.thread; class GlobalVarManager { private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){ protected String initialValue() { return "hello"; } }; public static ThreadLocal<String> getglobalVars() { return globalVars; } } class ThreadRun implements Runnable { private ThreadLocal<String> t; private String str; ThreadRun(ThreadLocal<String> temp, String s) { this.t = temp; this.str = s; } @Override public void run() { System.out.println(Thread.currentThread()+"改變前:" + t.get()); t.set(str); System.out.println(Thread.currentThread()+"改變後:" + t.get()); } } public class ThreadLocalTry { public static void main(String[] args) { for (int i =1; i < 5; i++) { new Thread(new ThreadRun(GlobalVarManager.getglobalVars(), ""+i)).start(); } } }
結果以下:
Thread[Thread-0,5,main]改變前:hello Thread[Thread-1,5,main]改變前:hello Thread[Thread-0,5,main]改變後:1 Thread[Thread-2,5,main]改變前:hello Thread[Thread-1,5,main]改變後:2 Thread[Thread-2,5,main]改變後:3 Thread[Thread-3,5,main]改變前:hello Thread[Thread-3,5,main]改變後:4
上述案例也可按以下方式來實現:
package com.csu.test; class GlobalVarManager { private static ThreadLocal<String> globalVars = new ThreadLocal<String>(){ protected String initialValue() { return "hello"; } }; public static String getGlobalVars() { return globalVars.get(); } public static void setGlobalVars(String str) { globalVars.set(str); } } class ThreadRun implements Runnable { private String str = null; public ThreadRun(String temp) { str = temp; } @Override public void run() { System.out.println(Thread.currentThread()+"改變前:" + GlobalVarManager.getGlobalVars()); GlobalVarManager.setGlobalVars(str); System.out.println(Thread.currentThread()+"改變後:" + GlobalVarManager.getGlobalVars()); } } public class ThreadLocalTest { public static void main(String[] args) { for (int i = 1; i < 5; i++) { new Thread(new ThreadRun("" + i)).start(); } } }
結果以下:
Thread[Thread-3,5,main]改變前:hello Thread[Thread-2,5,main]改變前:hello Thread[Thread-1,5,main]改變前:hello Thread[Thread-0,5,main]改變前:hello Thread[Thread-1,5,main]改變後:2 Thread[Thread-2,5,main]改變後:3 Thread[Thread-3,5,main]改變後:4 Thread[Thread-0,5,main]改變後:1
4.ThreadLocal的實現機制
此部份內容暫沒有深刻研究,欲瞭解更多內容請參考https://www.cnblogs.com/dennyzhangdd/p/7978455.html
(1)get()方法源碼以下:
(2)set()方法源碼以下:
(3)remove()方法源碼以下:
(4)上述幾個函數涉及到以下兩個函數
從前述源碼能夠看出,ThreadLocal的get、set、remove方法都是操做當前線程,而從Thread的源碼能夠看出該類有一個ThreadLocal.ThreadLocalMap類型的變量threadLocals,該變量在初次調用ThreadLocal的set()方法時經過createMap()方法初始化
5.ThreadLocalMap
ThreadLocalMap的部分源碼以下:
/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */ static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; /** * The number of entries in the table. */ private int size = 0; /** * The next size value at which to resize. */ private int threshold; // Default to 0 /** * Set the resize threshold to maintain at worst a 2/3 load factor. */ private void setThreshold(int len) { threshold = len * 2 / 3; } /** * Increment i modulo len. */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } /** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */ ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } /** * Construct a new map including all Inheritable ThreadLocals * from given parent map. Called only by createInheritedMap. * * @param parentMap the map associated with parent thread. */ private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
此處重點關注一下ThreadLocalMap中的幾個成員變量及方法
(1)private Entry[] table;
table是一個Entry類型的數組,該變量在ThreadLocalMap的構造函數中初始化
Entry是ThreadLocalMap的一個內部類
(2)set()方法
/** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
(3)getEntry()方法
/** * Get the entry associated with key. This method * itself handles only the fast path: a direct hit of existing * key. It otherwise relays to getEntryAfterMiss. This is * designed to maximize performance for direct hits, in part * by making this method readily inlinable. * * @param key the thread local object * @return the entry associated with key, or null if no such */ 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); } /** * Version of getEntry method for use when key is not found in * its direct hash slot. * * @param key the thread local object * @param i the table index for key's hash code * @param e the entry at table[i] * @return the entry associated with key, or null if no such */ private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
(4)remove()方法
/** * Remove the entry for key. */ private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
6.總結
ThreadLocal通常都是聲明在靜態變量中,若是不斷地建立ThreadLocal而沒有調用其remove方法,將致使內存泄露,特別是在高併發的Web容器當中。
ThreadLocal在處理線程的局部變量時比synchronized同步機制解決線程安全問題更簡單,更方便,且程序擁有更高的併發性。