類ThreadLocal的使用與源碼分析
變量值的共享可以使用public static的形式,所有的線程都使用同一個變量。如果每個線程都有自己的共享變量,就可以使用ThreadLocal。比如Hibernat的session問題就是存在ThreadLoca中。
類ThreadLocal主要解決的就是每個線程綁定自己的值,可以將ThreadLocal比喻成存放數據的盒子,盒子中可以存儲每個線程的私有數據。
而且ThreadLocal一般用作靜態成員變量封裝在工具類中實現線程隔離數據。在JavaEE結構中就是從Action層到Dao層可以使用threadLocal實現共享數據,並且線程之間相互隔離。(對於ThreadLocal,每個線程存進去的東西與取出來的是一致的,不會出現相互覆蓋的現象。)
1. 方法 get()與null
ThreadLocal的基本使用方法。
package cn.qlq.thread.ten; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ThreadLocal的基本使用 * * @author QiaoLiQiang * @time 2018年12月15日下午9:00:19 */ public class Demo1 { public static ThreadLocal<String> t1 = new ThreadLocal<String>();private static final Logger LOGGER = LoggerFactory.getLogger(Demo1.class); public static void main(String[] args) { if (t1.get() == null) { LOGGER.info("從未放過值"); t1.set("存放的值"); } LOGGER.info("{}", t1.get()); LOGGER.info("{}", t1.get()); } }
結果:
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 從未放過值
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 存放的值
21:02:38 [cn.qlq.thread.ten.Demo1]-[INFO] 存放的值
從第一個的返回結果看,第一次調用t1對象的get()方法時返回的值是null,通過set()賦值之後可以取出值。類ThreadLocal解決的是變量在不同線程間的隔離性,也就是每個線程擁有自己的值,不同線程中的值是可以放入ThreadLocal類中進行保存的。
2.驗證變量間的隔離性
驗證其隔離性,每個線程在變量間存放的值不同。
package cn.qlq.thread.ten; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ThreadLocal的基本使用 * * @author QiaoLiQiang * @time 2018年12月15日下午9:00:19 */ public class Demo2 { public static ThreadLocal<String> t1 = new ThreadLocal<String>(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo2.class); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { if (t1.get() == null) { LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName()); t1.set("存放的值" + Thread.currentThread().getName()); } LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); } }, "thread1").start(); new Thread(new Runnable() { @Override public void run() { if (t1.get() == null) { LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName()); t1.set("存放的值" + Thread.currentThread().getName()); } LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); } }, "thread2").start(); } }
結果: (由結果可以看出每個線程存放了不同的值,但是在獲取值的時候,每個線程又獲取到了不同的值。)
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] 從未放過值,threadName->thread2
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] 從未放過值,threadName->thread1
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] threadName - >thread2,值->存放的值thread2
21:11:08 [cn.qlq.thread.ten.Demo2]-[INFO] threadName - >thread1,值->存放的值thread1
3.解決get()返回null問題
為了解決返回為null的問題,也就是在get()的時候直接就返回默認值,采用繼承ThreadLocal並且重寫initialValue的方式實現。(初始值的時候也可以實現線程的隔離線)
package cn.qlq.thread.ten; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 解決get()返回null的問題 * * @author QiaoLiQiang * @time 2018年12月15日下午9:16:17 */ public class Demo3<T> extends ThreadLocal<String> { public static Demo3<String> t1 = new Demo3<String>(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { if (t1.get() == null) { LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName()); t1.set("存放的值" + Thread.currentThread().getName()); } LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); } }, "thread1").start(); new Thread(new Runnable() { @Override public void run() { if (t1.get() == null) { LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName()); t1.set("存放的值" + Thread.currentThread().getName()); } LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); } }, "thread2").start(); } @Override protected String initialValue() { return "這是初始值" + Thread.currentThread().getName(); } }
結果:
21:23:54 [cn.qlq.thread.ten.Demo3]-[INFO] threadName - >thread2,值->這是初始值thread2
21:23:54 [cn.qlq.thread.ten.Demo3]-[INFO] threadName - >thread1,值->這是初始值thread1
4. ThreadLocal中存入多個對象
有時候我們在ThreadLocal 中希望共享多個變量。
最簡單的一種辦法創建一個ThreadLocal就是將所有共享的數據存入一個Map,將Map存入ThreadLocal,另一種辦法就是所有共享數據放入一個bean中將bean存入ThreadLocal。
另一種辦法就是每個創建多個ThreadLocal分別存放多種共享的數據。
如下一個ThreadLocal存入Map中實現共享多個數據
package cn.qlq.thread.ten; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings("all") public class Demo7 { public static ThreadLocal t1 = new ThreadLocal(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class); public static void main(String[] args) throws InterruptedException { Map data = new HashMap(); data.put("str", "111222"); data.put("int", 11122); data.put("obj", new Object()); t1.set(data); Object object = t1.get(); System.out.println(object); } }
結果:
{str=111222, int=11122, obj=java.lang.Object@77700f3d}
或者多個ThreadLocal共享多個數據
package cn.qlq.thread.ten; import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings("all") public class Demo7 { public static ThreadLocal t1 = new ThreadLocal(); public static ThreadLocal<String> t2 = new ThreadLocal<String>(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class); public static void main(String[] args) throws InterruptedException { Map data = new HashMap(); data.put("str", "111222"); data.put("int", 11122); data.put("obj", new Object()); t1.set(data); t2.set("t2"); System.out.println(t1.get()); System.out.println(t2.get()); } }
結果:
{str=111222, int=11122, obj=java.lang.Object@271455a2}
t2
5. 類InheritableThreadLocal的使用
5.1值繼承
使用InheritableThreadLocal可以在子線程從父線程中繼承值。主線程存入值,在子線程中獲取。
package cn.qlq.thread.ten; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 主線程中設置值,子線程中獲取值 * * @author QiaoLiQiang * @time 2018年12月15日下午9:29:40 * @param <T> */ public class Demo4<T> extends InheritableThreadLocal<String> { public static Demo4<String> t1 = new Demo4<String>(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class); public static void main(String[] args) { // 主線程中存入值 t1.set("存放的值" + Thread.currentThread().getName()); new Thread(new Runnable() { @Override public void run() { if (t1.get() == null) { LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName()); t1.set("存放的值" + Thread.currentThread().getName()); } LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); } }, "thread1").start(); new Thread(new Runnable() { @Override public void run() { if (t1.get() == null) { LOGGER.info("從未放過值,threadName->{}", Thread.currentThread().getName()); t1.set("存放的值" + Thread.currentThread().getName()); } LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); } }, "thread2").start(); } }
結果:
21:29:02 [cn.qlq.thread.ten.Demo4]-[INFO] threadName - >thread2,值->存放的值main
21:29:02 [cn.qlq.thread.ten.Demo4]-[INFO] threadName - >thread1,值->存放的值main
測試在子線程中再次創建子線程。(值會一直繼承下去,對自己的子線程創建的子線程也有效)
package cn.qlq.thread.ten; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 主線程中設置值,子線程中獲取值 * * @author QiaoLiQiang * @time 2018年12月15日下午9:29:40 * @param <T> */ public class Demo5<T> extends InheritableThreadLocal<String> { public static Demo5<String> t1 = new Demo5<String>(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class); public static void main(String[] args) { // 主線程中存入值 t1.set("存放的值" + Thread.currentThread().getName()); // 創建子線程獲取值 new Thread(new Runnable() { @Override public void run() { LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); // 創建子子線程獲取值 new Thread(new Runnable() { @Override public void run() { LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); } }, "thread2").start(); } }, "thread1").start(); } }
結果:
21:32:34 [cn.qlq.thread.ten.Demo5]-[INFO] threadName - >thread1,值->存放的值main
21:32:34 [cn.qlq.thread.ten.Demo5]-[INFO] threadName - >thread2,值->存放的值main
5.2 值繼承再修改
值也可以被繼承再修改。
package cn.qlq.thread.ten; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 繼承再修改值 * * @author QiaoLiQiang * @time 2018年12月15日下午9:34:41 * @param <T> */ public class Demo6<T> extends InheritableThreadLocal<String> { public static Demo6<String> t1 = new Demo6<String>(); private static final Logger LOGGER = LoggerFactory.getLogger(Demo6.class); public static void main(String[] args) throws InterruptedException { // 主線程中存入值 t1.set("存放的值" + Thread.currentThread().getName()); LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); // 創建子線程獲取值 new Thread(new Runnable() { @Override public void run() { LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); // 主線程中存入值 t1.set("存放的值" + Thread.currentThread().getName()); LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); // 創建子子線程獲取值 new Thread(new Runnable() { @Override public void run() { LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); } }, "thread2").start(); } }, "thread1").start(); Thread.sleep(2 * 1000); LOGGER.info("threadName - >{},值->{}", Thread.currentThread().getName(), t1.get()); } }
結果:(主線程中存入值,在子線程修改了值,main線程取到的值還是main中存入的值,子線程以及子子線程獲得的值是子線程修改的值。)
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >main,值->存放的值main
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread1,值->存放的值main
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread1,值->存放的值thread1
21:36:26 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >thread2,值->存放的值thread1
21:36:28 [cn.qlq.thread.ten.Demo6]-[INFO] threadName - >main,值->存放的值main
6.ThreadLocal 源碼解析
ThreadLocal其實比較簡單,因為類裏就三個public方法:set(T value)、get()、remove()。
三個理論基礎:
1、每個線程都有一個自己的ThreadLocal.ThreadLocalMap對象 (ThreadLocalMap 是ThreadLocal的靜態內部類,一個類似於Map結構的普通類,沒有實現Map接口,也是內部維護一個靜態內部類Entry存放數據,而且其內部的Entry繼承 WeakReference弱引用(被若引用關聯的對象只能生存到下一次垃圾回收之前。其內部的key是Threadlocal,value就是存入的值)。)
Thread.class中的一個成員屬性:
ThreadLocal.ThreadLocalMap threadLocals = null;
2、每一個ThreadLocal對象都有一個循環計數器
3、ThreadLocal.get()取值,就是根據當前的線程,獲取線程中自己的ThreadLocal.ThreadLocalMap,然後在這個Map中根據第二點中循環計數器取得一個特定value值
6.1 set(T value)源碼解讀
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
可以看出是先獲取到當前線程,然後根據當前線程去獲取 ThreadLocalMap ,如果 獲取到的ThreadLocalMap不為空的話就直接set值,否則走 createMap方法創建map。
(1)getmap(t)從線程中獲取 ThreadLocalMap (上面說過了每個Thread都有都有一個自己的ThreadLocal.ThreadLocalMap對象)
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
(2)map.set(this,value)設置值
/** * 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(); }
/** * Returns the next hash code. */ private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
private final int threadLocalHashCode = nextHashCode(); /** * The next hash code to be given out. Updated atomically. Starts at * zero. */ private static AtomicInteger nextHashCode = new AtomicInteger();
這個Map存儲的方式不是鏈表法而是開地址法。看到設置table中的位置的時候,都把一個static的nextHashCode累加一下,這意味著,set的同一個value,可能在每個ThreadLocal.ThreadLocalMap中的table中的位置都不一樣。
- 先對ThreadLocal裏面的threadLocalHashCode取模獲取到一個table中的位置
- 這個位置上如果有數據,獲取這個位置上的ThreadLocal
(1)判斷一下位置上的ThreadLocal和我本身這個ThreadLocal是不是一個ThreadLocal,是的話數據就覆蓋,返回
(2)不是同一個ThreadLocal,再判斷一下位置上的ThreadLocal是是不是空的,這個解釋一下。Entry是ThreadLocalMap的一個靜態內部類,並且是弱引用,"static class Entry extends WeakReference<ThreadLocal>",有可能這個 Entry 被垃圾回收了,這時候把新設置的value替換到當前位置上,返回
(3)上面都沒有返回,給模加1,看看模加1後的table位置上是不是空的,是空的再加1,判斷位置上是不是空的...一直到找到一個table上的位置不是空的為止,往這裏面塞一個value。換句話說,當table的位置上有數據的時候,ThreadLocal采取的是辦法是找最近的一個空的位置設置數據。
3.這個位置上如果沒有數據,就創建一個Entry到這個位置。
(3) createMap(thread,value)方法查看:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
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); }
創建一個ThreadLocalMap並且將引用傳遞給線程對象的 threadLocals 。
ThreadLocalMap創建的時候做了一些初始化工作,並且將值設置進去。
6.2 get()源碼解讀
get()方法的源碼如下:
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(); }
獲取到當前線程-》獲取到當前線程裏面的ThreadLocalMap -》
如果ThreadLocalMap 為不為null,獲取其內部的Entry對象-》獲取entry的value(根據ThreadLocal 獲取一個下標,然後獲取對應下標的entry的信息)
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); }
如果ThreadLocalMap 為null,做默認設置並且返回默認值(這也是我們在上面的例子中繼承ThreadLocal重寫initialValue方法可以設置默認值的原因)
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
protected T initialValue() { return null; }
6.3 remove()源碼解讀
remove()源碼如下:
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
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; } } }
根據當前線程獲取到ThreadLocalMap-》如果獲取的ThreadLocalMap不為null,調用其remove(key)方法
remove(ThreadLocal)根據ThreadLocal 對象獲取一個下標i,如果tab[i]不為null,即存在對應的entry,調用entry的clear方法
補充:clear方法是Reference類型一個方法:--其作用就是將referent置為null,垃圾回收就可以回收此對象。
public void clear() { this.referent = null; }
類ThreadLocal的使用與源碼分析