日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ThreadLocal 和 InheritableThreadLocal

發布時間:2025/3/15 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ThreadLocal 和 InheritableThreadLocal 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在學習ThreadLocal之前,建議先了解Java中的4種引用

一、先看一下Thread,ThreadMap,ThreadLocal的關系

Thread中持有一個ThreadLocalMap ,這里你可以簡單理解為就是持有一個數組,這個數組是Entry 類型的。 Entry 的key 是ThreadLocal 類型的(準確說是一個指向ThreadLocal的引用),value 是Object 類型。也就是一個ThreadLocalMap 可以持有多個ThreadLocal。他們是一對多的關系

同時通過源碼我們可以看到,ThreadLocalMap是ThreadLocal里的一個靜態內部類,Entry又是ThreadLocalMap的一個靜態內部類。這里可以思考一下為何不直接使用HashMap?(HashMap節點之間是通過強引用關聯)

這里明明確一點:ThreadLocal不是隸屬于Thread的,他是一個獨立的(一個用于操作Thread中threadLocals屬性的工具類),使用他的時候,必須new出來,而不是通過Thread來獲得,明確這點,我感覺理解起來會好很多。

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;//可以看到Thread的threadLocals屬性底層是一個//由ThreadLocal類作為K,Object類作為v的Map結構Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
  • ThreadLocal的作用:為共享變量在每一個線程中創建一個副本,以保證不同的線程中拿到該變量的值都是不一樣的

二、ThreadLocal的基本方法

在實際使用之前我們先來了解一下ThreadLocal的幾個常用方法void

  • set(Object value)設置當前線程的線程局部變量的值。
public void set(T value) {Thread t = Thread.currentThread();//獲取當前執行的線程ThreadLocalMap map = getMap(t); //獲得當前線程的ThreadLocalMap實例if (map != null)//如果map不為空,說明當前線程已經有了一個ThreadLocalMap實例//注意這個方法,底層會覆蓋同一個k的v,也就是每一個Thread中的某個ThreaLocal的可以重復設置,但會覆蓋前面的值map.set(this, value);//直接將當前value設置到ThreadLocalMap中elsecreateMap(t, value); //說明當前線程是第一次使用線程本地變量,構造map }
  • 該方法返回當前線程所對應的線程局部變量。
public T get() {Thread t = Thread.currentThread();//獲得當前線程的ThreadLocalMap實例ThreadLocalMap map = getMap(t);if (map != null) {//查詢當前ThreadLocal變量實例對應的EntryThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")//返回對應Entry的valueT result = (T)e.value;return result;}}//如果map為null,即還沒有初始化,走初始化方法return setInitialValue();} --------------------------------------------------------private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.refersTo(key))return e;elsereturn getEntryAfterMiss(key, i, e);} ----------------------------------------------------------------- private T setInitialValue() {T value = initialValue();//protected方法,用戶可以重寫Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null){//如果map不為null,把初始化value設置進去map.set(this, value);}else{//如果map為null,則new一個map,并把初始化value設置進去createMap(t, value);}if (this instanceof TerminatingThreadLocal) {TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);}return value; }
  • public void remove()

將當前線程局部變量的值刪除,目的是為了減少內存的占用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束后,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量并不是必須的操作,但它可以加快內存回收的速度。

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {//根據當前ThreadLocal變量刪除Thread.ThreadLocals中對應的值m.remove(this);}}
  • protected Object initialValue()

返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是為了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,并且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。

三、引用ThreadLocal時示意圖

代碼:

/*** @author Cristianoxm*/ public class TestThreadLocal {static ThreadLocal<String> threadLocal1=new ThreadLocal<String>();static ThreadLocal<Integer> threadLocal2=new ThreadLocal<Integer>();public static void main(String[] args) {Thread [] runs=new Thread[3];for (int i=0;i<runs.length;i++){new MyThread(i).start();}}public static class MyThread extends Thread{int id;public MyThread(int id){this.id=id;}@Overridesynchronized public void run(){threadLocal1.set("線程-"+id);if(id == 1){threadLocal2.set(1);}System.out.println(Thread.currentThread().getName()+"編號:"+threadLocal1.get());System.out.println(threadLocal2.get());}} }

內存圖大概如下:只有Thread-1中有指向Threadlocal2的Entry,thread-2中是沒有的

四、底層原理解析

掌握了基本用法之后,我們就來聊聊ThreadLocal 的底層原理吧。在java中每一個線程都會有一個 ThrealLocalMap ,里面存放了一個一個的鍵值對(我們一般稱之為Entry),鍵值對的key 就是 Threadlocal本身,而value 就是他的值,正如下圖所示。

我們仔細的看一下這張圖。左邊是stack(棧),棧里面有用戶線程和threadlocal的引用。而在堆里,threadlocal 用一個弱引用指向了我們ThrealLocalMap 的key,而用戶線程 則是一個強引用指向了 這個enrty的 value.現在我們就能理解為什么threadlocal 的副本是怎么回事了,線程拷貝一份值,用Threadlocal自身做key ,保存在一個map中,從而實現了每個線程之間變量的互相隔離。

五、內存泄露分析

提到Threadlocal 必然不得不說他的內存泄露問題,首先我們注意下,threadlocalmap 是一個弱引用,也就是說只要gc,我們上面這條 ThreadLocal->key 的虛線就回被打斷,但是如果我們的當前線程沒有結束,那么指向value的強引用自然不會斷開。于是當gc發生的時候,我們的map中就出現了一個,key為null的 entry,但是這個entry 由于value的強引用并不會被回收,那么map中就出現了一個永遠訪問不到的entry!內存泄漏由此產生。當然,threadLocal 對此還是有對策的,當我們調用 get set 和 remove 方法的時候,threadlocal都會去檢查是否有這樣的entry并把他們清除掉。但是注意,除了 remove方法,其他兩個方法都是不可靠的,他們并非及時同時不一定會去執行清除方法。

  • 對象引用圖:

所以為了防止內存泄露,我們必須在使用完Threadlocal后及時的去調用remove方法!所以:ThreadLocal內存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應key就會導致內存泄漏,而不是因為弱引用。想要避免內存泄露就要手動remove()掉!

  • 那么:為什么使用弱引用

說道這里可能很多人會問了,為什么threadlocal要采用弱引用,采用強引用不是就沒有這個訪問不到entry的毛病了嗎。那我們這里就假設一下,如果這里的key是強引用的話,如果我們給這個Threadlocal 賦值為null。那么情況就如下圖所示

請大家注意這里 指向我們value的線已經斷了,照理來說這個entry應該被回收,但是由于我們的key是強引用,所以這個entry就變得和 Threadlocal同生共死了,從而造成內存泄露!也即是:entry中的value置空,threadlocal置空,那么這個空的entry會因為key的強引用,變得和 Threadlocal同生共死,就內存泄露了

  • 總結使用ThreadLocal時會發生內存泄漏的前提條件:
  • ①ThreadLocal引用被設置為null,且后面沒有set,get,remove操作。
  • ②線程一直運行,不停止。(線程池)
  • ③觸發了垃圾回收。(Minor GC或Full GC)

我們看到ThreadLocal出現內存泄漏條件還是很苛刻的,所以我們只要破壞其中一個條件就可以避免內存泄漏,單但為了更好的避免這種情況的發生我們使用ThreadLocal時遵守以下兩個小原則

  • ①ThreadLocal申明為private static final。
    Private與final 盡可能不讓他人修改變更引用,
    Static 表示為類屬性,只有在程序結束才會被回收。
  • ②ThreadLocal使用后務必調用remove方法。
    最簡單有效的方法是使用后將其移除。

六、流程圖

七、ThreadLocal的應用

  • 管理Connection
    最典型的是管理數據庫的Connection:當時在學JDBC的時候,為了方便操作寫了一個簡單數據庫連接池,需要數據庫連接池的理由也很簡單,頻繁創建和關閉Connection是一件非常耗費資源的操作,因此需要創建數據庫連接池,那么,數據庫連接池的連接怎么管理呢??我們交由ThreadLocal來進行管理。為什么交給它來管理呢??ThreadLocal能夠實現當前線程的操作都是用同一個Connection,保證了事務!
public class DBUtil {//數據庫連接池private static BasicDataSource source;//為不同的線程管理連接private static ThreadLocal<Connection> local;static {try {//加載配置文件Properties properties = new Properties();//獲取讀取流InputStream stream = DBUtil.class.getClassLoader().getResourceAsStream("連接池/config.properties");//從配置文件中讀取數據properties.load(stream);//關閉流stream.close();//初始化連接池source = new BasicDataSource();//設置驅動source.setDriverClassName(properties.getProperty("driver"));//設置urlsource.setUrl(properties.getProperty("url"));//設置用戶名source.setUsername(properties.getProperty("user"));//設置密碼source.setPassword(properties.getProperty("pwd"));//設置初始連接數量source.setInitialSize(Integer.parseInt(properties.getProperty("initsize")));//設置最大的連接數量source.setMaxActive(Integer.parseInt(properties.getProperty("maxactive")));//設置最長的等待時間source.setMaxWait(Integer.parseInt(properties.getProperty("maxwait")));//設置最小空閑數source.setMinIdle(Integer.parseInt(properties.getProperty("minidle")));//初始化線程本地local = new ThreadLocal<>();} catch (IOException e) {e.printStackTrace();}}public static Connection getConnection() throws SQLException {if(local.get()!=null){return local.get();}else{//獲取Connection對象Connection connection = source.getConnection();//把Connection放進ThreadLocal里面local.set(connection);//返回Connection對象return connection;}}//關閉數據庫連接public static void closeConnection() {//從線程中拿到Connection對象Connection connection = local.get();try {if (connection != null) {//恢復連接為自動提交connection.setAutoCommit(true);//這里不是真的把連接關了,只是將該連接歸還給連接池connection.close();//既然連接已經歸還給連接池了,ThreadLocal保存的Connction對象也已經沒用了local.remove();}} catch (SQLException e) {e.printStackTrace();}} }

同樣的,Hibernate對Connection的管理也是采用了相同的手法(使用ThreadLocal,當然了Hibernate的實現是更強大的)

八、InheritableThreadLocal

InheritableThreadLocal類其實是重寫了ThreadLocal的3個函數:

/*** 該函數在父線程創建子線程,向子線程復制InheritableThreadLocal變量時使用*/protected T childValue(T parentValue) {return parentValue;}/*** 由于重寫了getMap,操作InheritableThreadLocal時,* 將只影響Thread類中的inheritableThreadLocals變量,* 與threadLocals變量不再有關系*/ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}/*** 類似于getMap,操作InheritableThreadLocal時,* 將只影響Thread類中的inheritableThreadLocals變量,* 與threadLocals變量不再有關系*/void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
  • 作用:我們知道:當前線程的ThreadLocalMap,主要存儲該線程自身的ThreadLocal。而InheritableThreadLocal,是自父線程集成而來的ThreadLocalMap,主要用于父子線程間ThreadLocal變量的傳遞。 即主要存儲可自動向子線程中傳遞的ThreadLocal.ThreadLocalMap

九、inheritThreadLocals的使用

  • thread的創建
Thread thread = new Thread(); ---------------------------------------------- public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);} /*** 默認情況下,設置inheritThreadLocals可傳遞*/private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);} /*** 初始化一個線程.* 此函數有兩處調用,* 1、上面的 init(),不傳AccessControlContext,inheritThreadLocals=true* 2、傳遞AccessControlContext,inheritThreadLocals=false*/private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {......(其他代碼)if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);......(其他代碼)}

可以看到,采用默認方式產生子線程時,inheritThreadLocals=true;若此時父線程inheritableThreadLocals不為空,則將父線程inheritableThreadLocals傳遞至子線程。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);} /*** 構建一個包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap,該函數只被 createInheritedMap() 調用.*/private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);// ThreadLocalMap 使用 Entry[] table 存儲ThreadLocaltable = new Entry[len];// 逐一復制 parentMap 的記錄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) {// 可能會有同學好奇此處為何使用childValue,而不是直接賦值,// 畢竟childValue內部也是直接將e.value返回;// 個人理解,主要為了減輕閱讀代碼的難度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可知,子線程將parentMap中的所有記錄逐一復制至自身線程。

  • 代碼例子
public class TestInheritableThreadLocal {public static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();public static String get() {return threadLocal.get();}public static void set(String value) {threadLocal.set(value);}public static String inheritableThreadLocalGet() {return inheritableThreadLocal.get();}public static void inheritableThreadLocalSet(String value) {inheritableThreadLocal.set(value);}public static void main(String[] args) {for (int i = 0; i < 5; i++) {TestInheritableThreadLocal.set("ye");Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName() + ":" + TestInheritableThreadLocal.get()));t.start();}for (int i = 0; i < 5; i++) {TestInheritableThreadLocal.inheritableThreadLocalSet("ye");Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName() + ":" + TestInheritableThreadLocal.inheritableThreadLocalGet()));t.start();}} }

參考文章
參考文章

總結

以上是生活随笔為你收集整理的ThreadLocal 和 InheritableThreadLocal的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。