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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

Java 多线程 —— ThreadLocal

發(fā)布時(shí)間:2025/3/12 java 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java 多线程 —— ThreadLocal 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

一、引言

ThreadLocal是Java幫助實(shí)現(xiàn)線程封閉性的典型手段。

作用:提供線程內(nèi)的局部變量,這種變量在線程的生命周期內(nèi)起作用,減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或組件之間一些公共變量的傳遞復(fù)雜度。同時(shí)也用來(lái)維護(hù)線程中的變量不被其他線程干擾。

這個(gè)類(lèi)能使線程中的某個(gè)值與保存值的對(duì)象關(guān)聯(lián)起來(lái)。ThreadLocal提供了get 與set方法,這些方法為每個(gè)使用該變量的線程都存有一份獨(dú)立的副本,因此get總是返回由當(dāng)前執(zhí)行線程在調(diào)用set時(shí)設(shè)置的最新值

二、ThreadLocal的簡(jiǎn)單應(yīng)用

?ThreadLocal是使用空間換時(shí)間,synchronized是使用時(shí)間換空間,比如在hibernate中session就存在于ThreadLocal中,避免synchronized的使用。

下面程序的輸出結(jié)果為null,因?yàn)閺腡hreadLocal中取出的對(duì)象一定是本線程中set的對(duì)象,別的線程無(wú)法取出,因?yàn)榫€程自己放入的對(duì)象只能自己取得,因此無(wú)需進(jìn)行加鎖處理,執(zhí)行效率上ThreadLocal比synchronized要高。

public class ThreadLocal_02 {static ThreadLocal<Person> tl = new ThreadLocal<>();public static void main(String[] args) {new Thread(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();}System.out.println(tl.get()); // output : null}).start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (Exception e) {e.printStackTrace();}tl.set(new Person("張三"));}).start();}static class Person {String name;public Person(String name) {this.name = name;}} }

三、對(duì)ThreadLocal的理解

ThreadLocal對(duì)象通常用于防止對(duì)可變的單例變量或全局變量進(jìn)行共享。

例如,在單線程應(yīng)用程序中可能會(huì)維持一個(gè)全局的數(shù)據(jù)庫(kù)連接,并在程序啟動(dòng)時(shí)初始化這個(gè)連接對(duì)象,從而避免在調(diào)用每個(gè)方法時(shí)都要傳遞一個(gè)Connection對(duì)象。由于JDBC的連接對(duì)象不一定是線程安全的,因此,當(dāng)多線程應(yīng)用程序在沒(méi)有協(xié)同的情況下使用全局變量時(shí),就不是線程安全的。通過(guò)將JDBC的連接保存到ThreadLocal對(duì)象中,每個(gè)線程都會(huì)擁有屬于自己的連接:

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){public Connection initialValue() {return DriverManager.getConnection(DB_URL);}};public static Connection getConnection() {return connectionHolder.get();}

在比如,當(dāng)某個(gè)頻繁執(zhí)行的操作需要一個(gè)臨時(shí)對(duì)象,例如一個(gè)緩沖區(qū),而同時(shí)又希望避免在每次執(zhí)行時(shí)都重新分配該臨時(shí)對(duì)象,就可以使用ThreadLocal。

四、ThreadLocal的實(shí)現(xiàn)原理

ThreadLocal內(nèi)部提供了四個(gè)對(duì)外開(kāi)放的接口方法,這也是用戶(hù)操作ThreadLocal對(duì)象的基本方法:

1、public T get() :取得線程局部變量

2、public void set(T value) :設(shè)置線程局部變量

3、public void remove() :刪除線程局部變量

4、protected T initialValue() :返回該線程局部變量初始值

思考:ThreadLocal的實(shí)例是如何為每一個(gè)線程維護(hù)變量副本的呢?

上圖來(lái)自http://www.importnew.com/22039.html

其實(shí),每一個(gè)線程Thread其內(nèi)部都維護(hù)一個(gè)ThreadLocal.ThreadLocalMap的實(shí)例對(duì)象(變量名為:threadLocals)。

你可以將這個(gè)ThreadLocalMap對(duì)象理解為一個(gè)Map,但實(shí)際上它是一個(gè)數(shù)組,一個(gè)以封裝了ThreadLocal為鍵,Object為值的元素的數(shù)組。也就是說(shuō)ThreadLocal本身不存儲(chǔ)值,它只是作為一個(gè)key來(lái)讓當(dāng)前線程從ThreadLocalMap中獲取value。值得注意的是,ThreadLocalMap是使用?ThreadLocal的弱引用作為?Key?的,弱引用的對(duì)象在GC時(shí)會(huì)被回收。

static class ThreadLocalMap {//map中的每個(gè)節(jié)點(diǎn)Entry,其鍵key是ThreadLocal并且還是弱引用static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}// 初始化容量為16,以為對(duì)其擴(kuò)充也必須是2的指數(shù)private static final int INITIAL_CAPACITY = 16;// 真正用于存儲(chǔ)線程的每個(gè)ThreadLocal的數(shù)組,將ThreadLocal和其對(duì)應(yīng)的值包裝為一個(gè)Entryprivate Entry[] table;///....其他方法和操作都和map類(lèi)似 }

由此,我們可以大概了解到了其線程局部變量的維護(hù)機(jī)制:為不同的線程創(chuàng)建不同的ThreadLocalMap,以線程本身作為區(qū)分,每個(gè)線程之間沒(méi)有任何聯(lián)系。

下面感興趣可以看一下get()、set()的源碼:

public T get() {Thread t = Thread.currentThread();//當(dāng)前線程ThreadLocalMap map = getMap(t);//獲取當(dāng)前線程對(duì)應(yīng)的ThreadLocalMapif (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);//獲取對(duì)應(yīng)ThreadLocal的變量值if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//若當(dāng)前線程還未創(chuàng)建ThreadLocalMap,則返回調(diào)用此方法并在其中調(diào)用createMap方法進(jìn)行創(chuàng)建并返回初始值。return setInitialValue(); } public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value); }

五、ThreadLocal內(nèi)存泄漏問(wèn)題

5.1 ThreadLocal為什么會(huì)內(nèi)存泄漏?

ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個(gè)ThreadLocal沒(méi)有外部強(qiáng)引用來(lái)引用它,那么系統(tǒng) GC 的時(shí)候,這個(gè)ThreadLocal勢(shì)必會(huì)被回收,這樣一來(lái),ThreadLocalMap中就會(huì)出現(xiàn)key為null的Entry,就沒(méi)有辦法訪問(wèn)這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束(如線程池的線程回收)的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value?永遠(yuǎn)無(wú)法回收,造成內(nèi)存泄漏。?

其實(shí),ThreadLocalMap的設(shè)計(jì)中已經(jīng)考慮到這種情況,也加上了一些防護(hù)措施:在ThreadLocal的get(),set(),remove()的時(shí)候都會(huì)清除線程ThreadLocalMap里所有key為null的value。

但這些被動(dòng)的預(yù)防措施并不能保證不會(huì)內(nèi)存泄漏。

5.2 為什么使用弱引用?

從表面上看內(nèi)存泄漏的根源在于使用了弱引用。網(wǎng)上的文章大多著重分析ThreadLocal使用了弱引用會(huì)導(dǎo)致內(nèi)存泄漏,但是另一個(gè)問(wèn)題也同樣值得思考:為什么使用弱引用而不是強(qiáng)引用?

我們先來(lái)看看官方文檔的說(shuō)法:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
為了應(yīng)對(duì)非常大和長(zhǎng)時(shí)間的用途,哈希表使用弱引用的 key。

下面我們分兩種情況討論:

  • key 使用強(qiáng)引用:引用的ThreadLocal的對(duì)象被回收了,但是ThreadLocalMap還持有ThreadLocal的強(qiáng)引用,如果沒(méi)有手動(dòng)刪除,ThreadLocal不會(huì)被回收,導(dǎo)致Entry內(nèi)存泄漏。
  • key 使用弱引用:引用的ThreadLocal的對(duì)象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使沒(méi)有手動(dòng)刪除,ThreadLocal也會(huì)被回收。value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除。

比較兩種情況,我們可以發(fā)現(xiàn):由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果都沒(méi)有手動(dòng)刪除對(duì)應(yīng)key,都會(huì)導(dǎo)致內(nèi)存泄漏,但是使用弱引用可以多一層保障:弱引用ThreadLocal不會(huì)內(nèi)存泄漏,對(duì)應(yīng)的value在下一次ThreadLocalMap調(diào)用set,get,remove的時(shí)候會(huì)被清除。

因此,ThreadLocal內(nèi)存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長(zhǎng),如果沒(méi)有手動(dòng)刪除對(duì)應(yīng)Key就會(huì)導(dǎo)致內(nèi)存泄漏,而不是因?yàn)槿跻谩?/p>

5.3 有效避免內(nèi)存泄漏的最佳實(shí)踐

每次使用完ThreadLocal,都調(diào)用它的remove()方法,清除數(shù)據(jù)。

六、鳴謝

《深入剖析ThreadLocal實(shí)現(xiàn)原理以及內(nèi)存泄漏問(wèn)題》

《深入分析 ThreadLocal 內(nèi)存泄漏問(wèn)題》

總結(jié)

以上是生活随笔為你收集整理的Java 多线程 —— ThreadLocal的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。