二十一、JAVA多執行緒筆記:執行緒上下文設計模式(ThreadLocal)
上下文是貫穿整個系統或階段生命週期的物件,其中包含了系統全域性的一些資訊,比如登入後的使用者資訊、賬號資訊,以及在程式每一個階段執行時的資料。
設計時要考慮到全域性唯一性,還要考慮有些成員只能被初始化一次。比如配置資訊載入。以及在多執行緒環境下,上下文成員的執行緒安全性
責任鏈模式
執行緒上下文模式
ThreadLocal詳解
簡介:
ThreadLocal類
實現思路
Thread類有一個型別為ThreadLocal.ThreadLocalMap的例項變數threadLocals,也就是說每個執行緒有一個自己的ThreadLocalMap。ThreadLocalMap有自己的獨立實現,可以簡單地將它的key視作ThreadLocal,value為程式碼中放入的值(實際上key並不是ThreadLocal本身,而是它的一個弱引用)。每個執行緒在往某個ThreadLocal裡塞值的時候,都會往自己的ThreadLocalMap裡存,讀也是以某個ThreadLocal作為引用,在自己的map裡找對應的key,從而實現了執行緒隔離。
使用場景
1. 在進行物件跨層訪問的時候,避免方法多次傳遞,打破層次間的約束。
2.執行緒間資料隔離
3.進行事物操作,用於儲存執行緒事物資訊。
ThreadLocal並不是解決多執行緒下共享資源的技術,一般情況下,每個執行緒的ThreadLocal儲存的都是一個全新的物件(通過new關鍵字建立),如果多個執行緒的ThreadLocal儲存了一個物件的引用,那麼其還是會面臨資源的競爭,資料不一致等併發問題
ThreadLocal方法詳解與原始碼分析
package com.zl.step21; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; import static java.lang.Thread.currentThread; public class ThreadLocalExample { public static void main(String[] args) { ThreadLocal<Integer> tlocal = new ThreadLocal<>(); IntStream.range(0,10).forEach( i -> { new Thread(()->{ try { tlocal.set(i); System.out.println(currentThread()+" set i : " + tlocal.get()); TimeUnit.SECONDS.sleep(1); System.out.println(currentThread()+" get i : " + tlocal.get()); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); }); } }
10個執行緒之前互相併不影響,每個執行緒存入ThreadLocal之間的i完全不同彼此獨立。
1.initialValue()方法
initialValue方法為ThreadLocal要儲存的資料型別指定了一個初始化值,在ThreadLocal中預設返回null
程式碼示例:
protected T initialValue() { return null; }
ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){ @Override protected Object initialValue(){ return new Object() ; } }; new Thread(()-> { System.out.println(threadLocal.get()); }).start(); System.out.println(threadLocal.get());
結果:
Connected to the target VM, address: '127.0.0.1:53305', transport: 'socket'
Disconnected from the target VM, address: '127.0.0.1:53305', transport: 'socket'
[email protected]
[email protected]
Process finished with exit code 0
2.set(T t) 方法
set方法主要是為了ThreadLocal指定將要被儲存的資料,如果重寫了initialValue() 方法, 在不呼叫set(T t)方法的時候,資料的初始值是initialValue()方法的計算結果,示例程式碼:
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { // 獲取當前執行緒 Thread t = Thread.currentThread(); // 根據當前執行緒獲取ThreadLocalMap資料結構 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
/** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { // 以當前threadLocal為key 存放的資料為value t.threadLocals = new ThreadLocalMap(this, firstValue); }
** * 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); // 在map的set方法中遍歷整個map的entry,如果發現ThreadLocal相同,直接替換 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //ThreadLocal相同,直接替換 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.get() 方法
get用於返回當前執行緒在ThreadLocal中的資料備份,當前執行緒的資料存放在一個稱為ThreadLocalMap的資料結構中
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { //獲取當前執行緒 Thread t = Thread.currentThread(); // 獲取ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { //設定返回值 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 初始化值 return setInitialValue(); }
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; }
ThreadLocalMap
完全類似於HashMap的資料結構,僅僅用於存放執行緒存放在ThreadLocal中的資料備份,ThreadLocalMap的所有方法對外都不可見
ThreadLocalMap中用於儲存的事Entry,他是一個WeakReference(弱引用)型別的子類。為了在垃圾會受到時候,自動回收,防止記憶體溢位的情況出現。
/** * 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; } }
ThreadLocal的記憶體洩漏問題分析
1.WeakReference(弱引用)在JVM觸發任意GC 都會導致Entry的回收。完全是由HashMap充當的。
2.在get資料時增加檢查,清除已經被垃圾回收期回收的Entry
3.在set資料時增加檢查,清除已經被垃圾回收期回收的Entry
ThreadLocal實現上下文
第一版:
package com.zl.step21; public class ActionContext { private static final ThreadLocal<Context> context = ThreadLocal.withInitial(Context::new) ; public static Context get(){ return context.get(); } static class Context{ private Configuration configuration ; private OtherResource otherResource ; public Configuration getConfiguration(){ return configuration ; } public void setConfiguration(Configuration configuration){ this.configuration = configuration ; } public OtherResource getOtherResource(){ return otherResource ; } public void setOtherResource(OtherResource otherResource){ this.otherResource = otherResource ; } } } class Configuration {} class OtherResource {}
第二版:
package com.zl.step21; public class ActionContext { private static final ThreadLocal<Configuration> configuration = ThreadLocal.withInitial(Configuration::new) ; private static final ThreadLocal<OtherResource> otherResource = ThreadLocal.withInitial(OtherResource::new) ; public Configuration getConfiguration(){ return configuration.get() ; } public void setConfiguration(Configuration conf){ configuration.set(conf) ; } public OtherResource getOtherResource(){ return otherResource.get(); } public void setOtherResource(OtherResource oResource){ otherResource.set(oResource) ; } } class Configuration {} class OtherResource {}
ThreadLocal將指定的變數和當前執行緒繫結,執行緒間彼此隔離,持有不同物件的例項,避免競爭。
ThreadLocal存在記憶體洩漏的問題。