詳解Java中ThreadLocal類型及簡單用法
1 基本概念
ThreadLocal類提供瞭線程局部變量。這些變量與普通變量的不同之處在於,每個訪問一個變量(通過其get或set方法)的線程都有自己的、獨立初始化的變量副本。ThreadLocal實例通常是希望將狀態與線程關聯起來的類中的私有靜態字段(例如,用戶ID或事務ID)。
例如,下面的類生成每個線程本地的唯一標識符。 線程的id在第一次調用ThreadId.get()時被賦值,並且在後續調用中保持不變。
public class ThreadId { // 包含要分配的下一個線程ID的原子整數 private static final AtomicInteger nextId = new AtomicInteger(0); // 包含每個線程ID的線程局部變量 private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); } }; // 返回當前線程的唯一ID,並在必要時賦值 public static int get() { return threadId.get(); } }
隻要線程是活的並且ThreadLocal實例是可訪問的,每個線程都持有一個對線程局部變量副本的隱式引用; 當一個線程離開後,它的所有線程本地實例副本都將被垃圾收集(除非存在對這些副本的其他引用)
2 簡單使用
private static ThreadLocal<String> threadLocal = new ThreadLocal(); private static void print(String thread) { //打印當前線程中本地內存中本地變量的值 System.out.println(thread + " :" + threadLocal.get()); //清除本地內存中的本地變量 threadLocal.remove(); } public static void main(String[] args) { new Thread(() -> { //設置線程1副本變量的值 threadLocal.set("I am Thread1"); //調用打印方法 print("thread1"); //打印本地變量 System.out.println("after remove : " + threadLocal.get()); }).start(); new Thread(() -> { //設置線程2副本變量的值 threadLocal.set("I am Thread2"); //調用打印方法 print("thread2"); System.out.println("after remove : " + threadLocal.get()); }).start(); }
運行結果:
thread1 :I am Thread1
thread2 :I am Thread2
after remove : null
after remove : null
由上邊的程序可以看出,ThreadLocal就是將本地變量在多線程訪問條件下給每個線程一個副本變量,圖示:
3 應用場景
最典型應用場景就是Spring的聲明式事務、 解決數據庫連接、Session管理
那數據庫鏈接為例:
將數據庫的鏈接示例在每個線程中都有一份副本數據,在某一個線程關閉連接的時候就不會關閉其他鏈接,session同理。
private static final ThreadLocal<Connection> connection = new ThreadLocal() { @Override protected Object initialValue() { return Connection.getConnection(); } }; private static Connection getConnections() { return connection.get(); } public static void main(String[] args) { new Thread(() -> { Connection connection = getConnections(); //不會關閉其他鏈接 connection.close(); }).start(); new Thread(() -> { Connection connection = getConnections(); connection.close(); }).start(); }
4 底層原理
ThreadLocal類型主要有3個方法和一個數據結構,分別是get()、set(Object)、remove()及ThreadLocalMap
4.1 set(Object)
將該線程局部變量的當前線程副本設置為指定的值。 大多數子類都不需要重寫這個方法,隻依賴於initialValue方法來設置線程局部變量的值。
參數: Value -要存儲在當前線程本地線程的副本中的值。
public void set(T value) { //獲取調用者線程 Thread t = Thread.currentThread(); //以當前線程作為key值,去查找對應的線程變量,找到對應的map ThreadLocalMap map = getMap(t); //如果map不為null,就直接添加本地變量,key為當前定義的ThreadLocal變量的this引用,值為添加的本地變量值 if (map != null) map.set(this, value); //如果map為null,說明首次添加,需要首先創建出對應的map else createMap(t, value); }
4.2 get()
返回該線程局部變量的當前線程副本中的值。 如果變量在當前線程中沒有值,則首先將其初始化為initialValue方法調用所返回的值。
返回: 這個線程本地的當前線程值
public T get() { Thread t = Thread.currentThread(); //獲取當前線程的threadLocals變量 ThreadLocalMap map = getMap(t); //如果threadLocals變量不為null,就可以在map中查找到本地變量的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //執行到此處,threadLocals為null,調用該更改初始化當前線程的threadLocals變量 return setInitialValue(); } private T setInitialValue() { //protected T initialValue() {return null;} T value = initialValue(); //獲取當前線程 Thread t = Thread.currentThread(); //以當前線程作為key值,去查找對應的線程變量,找到對應的map ThreadLocalMap map = getMap(t); //如果map不為null,就直接添加本地變量,key為當前線程,值為添加的本地變量值 if (map != null) map.set(this, value); //如果map為null,說明首次添加,需要首先創建出對應的map else createMap(t, value); return value; }
4.3 remove()
移除此線程局部變量的當前線程值。如果這個線程局部變量隨後被當前線程讀取,它的值將通過調用它的initialValue方法重新初始化,除非它的值是由當前線程在中間設置的。這可能導致在當前線程中多次調用initialValue方法。
public void remove() { //獲取當前線程綁定的threadLocals ThreadLocalMap m = getMap(Thread.currentThread()); //如果map不為null,就移除當前線程中指定ThreadLocal實例的本地變量 if (m != null) m.remove(this); }
4.4 ThreadLocalMap
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; //有效Entry數組 private Entry[] table; //大小 private int size = 0; //負載因子 private int threshold; // Default to 0 }
5 內存泄漏隱患和防止策略
5.1 為什麼會發生內存泄漏?
在線程池中線程的存活時間太長,往往都是和程序同生共死的,這樣 Thread 持有的 ThreadLocalMap 一直都不會被回收,再加上 ThreadLocalMap 中的 Entry 對 ThreadLocal 是弱引用(WeakReference),所以隻要 ThreadLocal 結束瞭自己的生命周期是可以被回收掉的。
Entry 中的 Value 是被 Entry 強引用的,即便 value 的生命周期結束瞭,value 也是無法被回收的,導致內存泄露。
5.2 怎樣防止內存泄漏?
- ThreadLocal申明為private static final xxx
- ThreadLocal使用後務必調用remove方法。
參考文章:
https://www.cnblogs.com/fsmly/p/11020641.html
https://blog.csdn.net/meism5/article/details/90413860
https://blog.csdn.net/zzg1229059735/article/details/82715741
到此這篇關於詳解Java中ThreadLocal類型及簡單用法的文章就介紹到這瞭,更多相關Java中ThreadLocal類型內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java ThreadLocal類使用詳解
- Java中ThreadLocal線程變量的實現原理
- Java線程變量ThreadLocal源碼分析
- ThreadLocal原理介紹及應用場景
- Java面試必問之ThreadLocal終極篇分享