ThreadLocal原理解析以及是否需要調用remove方法

平常的開發過程中,如果有個類不是線程安全的,比如SimpleDateFormat,要使這個類在并發的過程中是線程安全的,那么可以將變量設置位局部變量,不過存在的問題就是頻繁的創建對象,對性能和資源會有一定降低和消耗;那么這里就可以用到ThreadLocal作為線程隔離,那么ThreadLocal是如何實現線程與線程之間隔離的呢,待會兒會在下文進行講解。

之前有篇使用ThreadLocal做線程之間的隔離實例,大家可以參考一下:使用ThreadLocal實現Mybatis多數據源

JDK引用類型

在了解ThreadLocal原理之前,先讓大家了解一下JDK中的引用類型。

JDK共有四種引用類型:

1、強引用類型:就是平常創建的對象都屬于強引用類型,比如 Object object = new Object();該object為強引用類型,如果該引用沒有主動置為null,那么該引用的對象就不會被GC回收,所以一般在寫完一段業務之后都會將用到的對象引用置為null,就是為了輔助GC更好的進行垃圾回收。

2、軟引用類型:比強引用的類型弱一點,在應用程序發生OOM(內存溢出)之前就會去回收這些弱引用占用的內存,使用的SoftReference類,使用示例如下:

?3、弱引用類型:比軟引用類型還要弱一點,在下一次發生GC回收之前就會被垃圾回收器進行回收,使用WeakReference類,使用示例如下:

4、虛引用類型:這個是JDK中最弱的引用類型,在對象被回收之前會移入到一個隊列當中,然后在進行刪除,這個引用類型用的不多,在JDK中引用的類是PhantomReference,這個需結合引用隊列(ReferenceQueue)以及重寫finalize方法進行使用,使用示例如下:

?

?對于應用場景來說,如果當前對象為可有可無的話,那么可以使用軟引用或者弱引用進行使用,而對應虛引用的話,個人認為可以適用在非GC回收的區域(比如:元空間MetaSpace)使用,可以用來監測這些區域的回收情況等。

基本原理

ThreadLocal的用法呢,這里就先不談;底層使用的是ThreadLocalMap這個Map的底層采用的是ThreadLocalMap.Entry,ThreadLocalMap這個類在每個線程當中都會存在一份對應的單獨得對象,在Thread類中變量如下:

? ? /* ThreadLocal values pertaining to this thread. This map is maintained
? ? ?* by the ThreadLocal class. */
? ? ThreadLocal.ThreadLocalMap threadLocals = null;

這里就是線程安全的重點,因為并發的情況下,每個線程都對應著有份自己的ThreadLocalMap,所以就不存在多線程競爭資源問題,所以如果使用ThreadLocal建議和線程一起使用,以為這樣可以減少系統的性能開銷以及對ThreadLocal對象的一種復用,提升系統性能。

看一下ThreadLocalMap,會發現Entry繼承了WeakReference類,說明這個類創建出來的對象是弱引用對象

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
//容量
private static final int INITIAL_CAPACITY = 16;
//存儲屬性值
private Entry[] table;
//構造初始化table數組
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);
}

我們開看一下get方法:獲取當前線程,然后通過調用getMap方法獲取到當前線程的ThreadLocalMap對象,對于剛開始初始化的線程或者在ThreadLocalMap中沒有找到來說,那么就會走下面的setInitialValue方法,如果已經初始化的ThreadLocalMap來說,會直接獲取對應的Entry對象

    /*** 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 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();}

在setInitialValue方法中,會發現去獲取初始化方法中的對象,如果在創建ThreadLocal沒有重寫初始化方法的話,那么就會返回null,或者在使用前調用set方法重新設置一下當前線程中的ThreadLocalMap中的屬性初始化值,大家可以各自去看一下set方法;

private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}

這里列一下getEntry方法,根據ThreadLocal對象計算出hash值找到對應的table數組的位置,并且獲取到這個對象。

private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);
}

問題

以為這就完了?并沒有,規范的使用為在使用過get方法應該在調用remove方法進行刪除(即將當前線程ThreadLocalMap中的引用置為空并且table數組中的Entry值也置為空);如果是線程池的話,那么這些數據就會一直存在,如果沒有及時刪除會造成內存泄漏。

調用remove方法回去執行ThreadLocalMap的remove方法,而在這個方法中,通過計算得到對應的Entry數組的位置,并且進行引用清除以及table數組清空,避免內存泄漏問題,

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
/*** Remove the entry for key.*/
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) {//將Entry本身的引用ThreadLocal置為空e.clear();//清空table數組中Entry.valueexpungeStaleEntry(i);return;}}
}

但是如果是經常性用到的ThreadLocal的話,個人建議可以不用刪除,因為如果頻繁使用的話,置為null,后面又會重新調用在ThreadLocalMap未找到,那么就會調用setInitialValue方法,重新創建對象并且賦值,在某種意義上可以說是和局部變量是一樣的了,這樣就違背了當初減少性能開銷的需求了。

在加上Entry繼承了WeakReference類,所以創建的對象會是個弱引用類型,在GC進行回收時候會被回收掉的,如果回收掉了引用對象,那么Entry中的value變量值是否還存在呢;

繼續解析

注意這里是回收掉引用的對應,即ref.get()為null值,但是弱引用本身這個對象是還在的,我們看一下在setInitialValue方法中是如何處理的,重新設置里面,如果獲取到ThreadLocal引用沒有獲取到,說明這個弱引用被回收了,這里就會去調用replaceStaleEntry方法。

private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);//重新設置值elsecreateMap(t, value);return value;
}
//ThreadLocalMapprivate void set(ThreadLocal<?> key, Object value) {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();
}

?而在replaceStaleEntry方法中有個這么一行代碼,將value變量置為null并且重新創建Entry對象,所以就算是沒有調用remove刪除方法,在GC過后依舊會置為null。

? ? ? ? ? ? // If key not found, put new entry in stale slot
? ? ? ? ? ? tab[staleSlot].value = null;
? ? ? ? ? ? tab[staleSlot] = new Entry(key, value);

在此會有個小問題,為啥不用SoftReference而是使用WeakReference,個人覺得如果使用軟引用的話,如果是使用線程池并且ThreadLocal會頻繁訪問的話,那么是可以的,但是實際應用并非只有這種情況,而且在發生OOM之前,只會回收掉軟引用對象,但是Entry中的value變量還在,并不能真正的回收掉值,只有等到下一次使用的時候才能置為null,所以綜合來看使用WeakReference還是最好的選擇。

歡迎各位大佬一起討論

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/537108.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/537108.shtml
英文地址,請注明出處:http://en.pswp.cn/news/537108.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Maven基礎及概念

前言 今天就來聊聊Maven的基礎和一些比較概念性的東西&#xff0c;還有一些常用的Maven命令啥的&#xff0c;主要是某人腦子記不住&#xff0c;記在博客中讓她自己看吧&#xff0c;省的費心給她找。 后續的文章會聊到Maven的一些比較高級用法&#xff0c;像自定義插件&#x…

織夢縮略圖自動補齊絕對路徑_織夢生成文章內容縮略圖時自動加上域名絕對路徑...

今天又接了個織夢CMS的有償服務,客戶想要后臺添加文章內容的時候,縮略圖自動變成帶上絕對路徑的格式.比如我們默認的縮略圖是這樣的 /uploads/allimg/150814/123P2NB-0-lp.png 他想要的效果是這樣的 http://www.youwujun.com.cn/uploads/allimg/150814/123P2NB-0-lp.png大家懂我…

BUAA 436 孟竹的復習計劃(二維樹狀數組)

題目鏈接&#xff1a;http://acm.buaa.edu.cn/problem/436/ 題意&#xff1a;一個數列兩種操作&#xff1a;&#xff08;1&#xff09;將某個位置的數字改成另一個數字&#xff1b;&#xff08;2&#xff09;交換兩個位置的數字。每次操作之后輸出逆序數的個數。 思路&#xff…

Maven之pom.xml常用標簽解析及鏡像配置

前言 Maven僅僅是個打包工具而已&#xff0c;個人覺得沒有太大必要花費在打包工具上&#xff0c;這里就列舉一下個人覺得會常用標簽的使用就好了&#xff0c;原理啥的基本就不太會去深度了解了&#xff0c;如果以后遇到需了解Maven工作原理的工作的話&#xff0c;到時候一定分…

idea 導入svn代碼_idea導入svn項目

起初和導入git項目一樣&#xff0c;file - new - project from version control - &#xff0c;這后面選 subversion。在打開的 checkout from subversion對話框中&#xff0c;輸入svn地址&#xff0c;比如 svn://11.22.33.44/demo。添加一個后&#xff0c;展開新加項&#xff…

由mysql8降級到mysql5

最近在研究liferay的使用。liferay可以連接mysql數據庫。電腦中裝的mysql的最新版本是mysql8。于是開始按照liferay的要求進行連接。但是多番嘗試后&#xff0c;均報錯&#xff1a;java.sql.SQLException: java.lang.ClassCastException: java.math.BigInteger cannot be cast …

tf計算矩陣維度_tensorflow中關于 多維tensor的運算(tf.multiply, tf.matmul, tf.tensordot)...

multiply 等同與* &#xff0c;用于計算矩陣之間的element-wise 乘法&#xff0c;要求矩陣的形狀必須一致(或者是其中一個維度為1)&#xff0c;否則會報錯&#xff1a;import tensorflow as tfa tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12], shape[2, 3, 2])b tf.con…

Maven高級之插件開發

前言 終于來到了Maven的插件開發&#xff0c;其實Maven的插件并沒有想象的那么難&#xff0c;剛開始講Maven基礎的時候就演示了一下JDK是如何打包的&#xff0c;Maven打包只是在JDK打包上封裝了一層而已&#xff0c;Maven也支持自定義插件開發 創建 我們先使用quickstart原型…

HTTP1.1新增了五種請求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 、 CONNECT

200 &#xff08;成功&#xff09; 服務器已成功處理了請求。 通常&#xff0c;這表示服務器提供了請求的網頁。 201 &#xff08;已創建&#xff09; 請求成功并且服務器創建了新的資源。 202 &#xff08;已接受&#xff09; 服務器已接受請求&#xff0c;但尚未處…

katalon進行app測試_Katalon API 測試 Demo

為何選擇Katalon符合我們當下的情況&#xff0c;測試需要借助現有工具提高測試效率以及提高測試質量&#xff1b;為何不自己寫代碼&#xff1f;不是只有自己寫的框架才是最好的&#xff0c;合適的才是最好的&#xff1b;katalon 支持ui、mobile、api 同時也支持腳本模式&#x…

Maven高級之archetype(原型/骨架)開發

前言 archetype這個的主要功能就是將寫好的項目模塊打包成一個原型&#xff0c;然后提供給其他人使用&#xff0c;這樣別人就可以快速使用這個項目模板了。 這個東西雖然很多人都基本用不上&#xff0c;但原型這個東西用的好還是很方便的&#xff0c;能夠在開發新項目上省去大…

深度學習在搜索業務中的探索與實踐

本文根據美團高級技術專家翟藝濤在2018 QCon全球軟件開發大會上的演講內容整理而成&#xff0c;內容有修改。引言 2018年12月31日&#xff0c;美團酒店單日入住間夜突破200萬&#xff0c;再次創下行業的新紀錄&#xff0c;而酒店搜索在其中起到了非常重要的作用。本文會首先介紹…

cesium面積計算_cesium-長度測量和面積測量

(更新)多謝網友的提醒&#xff0c;面積測量的小問題已經修改&#xff0c;經測試可正常使用網上找的大神的實現方法有點問題&#xff0c;實現有一些bug&#xff0c;作為cesium新手一個&#xff0c;棄之不忍&#xff0c;只好硬著頭皮修改了&#xff0c;不過還好問題不大&#xff…

SpringBoot自動配置原理流程

前言 新公司太忙了&#xff0c;都沒啥空更新博客&#xff0c;就隨便記錄一下以前的學習筆記吧。SpringBoot是基于Spring上的衍生框架&#xff0c;只要看懂了Spring的話&#xff0c;學這個就比較簡單了&#xff1b;SpringBoot也是在當前微服務時代下流行的框架&#xff0c;并且…

算法:對象方式數組去重

var arr [3, 1, 1, 4 , 2 , 4 , 2 , 4 , 2, 1, 1, 3, 3, 3];var ary[];var obj{};for(var i0;i<arr.length;i){var curarr[i];if(!obj[cur]){obj[cur]cur;ary.push(cur);}}console.log(ary); 復制代碼

python實現路由功能_python 實現重啟路由器

有一些服務&#xff0c;需要動態IP&#xff0c;所以我們用重啟路由器的方法實現。人工重啟不可選&#xff0c;用定時腳本執行即可。貼代碼&#xff0c;每種路由器&#xff0c;提示不一樣。需要路由器有telnet功能才行。#!/usr/bin/env python# -*- coding: utf-8 -*-import tel…

SpringBoot自定義Starter(自動配置類)

前言 SpringBoot其實從誕生以來圍繞的核心就是快速構建項目&#xff0c;快速構建的前提是有人幫你做好輪子&#xff0c;開發者只要拿來即用就好了&#xff0c;而造好輪子的人就是SpringBoot的開發者&#xff0c;引入自動配置的形式幫助開發者快速創建項目&#xff0c;而自動配…

Java并發編程之synchronized關鍵字解析

前言 公司加班太狠了&#xff0c;都沒啥時間充電&#xff0c;這周終于結束了。這次整理了Java并發編程里面的synchronized關鍵字&#xff0c;又稱為隱式鎖&#xff0c;與JUC包中的Lock顯示鎖相對應&#xff1b;這個關鍵字從Java誕生開始就有&#xff0c;稱之為重量級鎖&#xf…

raidrive安裝失敗_記一次RaiDrive映射OneDrive遇到的問題

大概在1周以前&#xff0c;出于需要存放直播錄像的原因&#xff0c;根據別人的視頻教程去自己動手搞了個5T網盤的帳號。(體驗一下&#xff0c;其實我還同時存一份在百度云&#xff0c;怕不穩定)用RaiDrive創建OneDrive的映射&#xff0c;在這步驟點確定后&#xff0c;會彈出微軟…

通過代理模式 + 責任鏈模式實現對目標執行方法攔截和增強功能

前言 最近需要實現一個插件功能&#xff0c;但是如果做成兩個接口的話&#xff08;即執行前和執行后&#xff09;&#xff0c;那么會降低插件的可玩性&#xff0c;所以需做成類似AOP的環繞通知形式&#xff0c;所以就使用到了責任鏈模式和代理模式進行實現。 介紹 代理模式(P…