ThreadLocal就是這么簡單

前言

今天要研究的是ThreadLocal,這個我在一年前學習JavaWeb基礎的時候接觸過一次,當時在baidu搜出來的第一篇博文ThreadLocal,在評論下很多開發者認為那博主理解錯誤,給出了很多有關的鏈接來指正(可原博主可能沒上博客了,一直沒做修改)。我也去學習了一番,可惜的是當時還沒有記錄的習慣,直到現在僅僅記住了一些當時學過的皮毛。

因此,做一些技術的記錄是很重要的~同時,ThreadLocal也是面試非常常見的面試題,對Java開發者而言也是一個必要掌握的知識點~

當然了,如果我有寫錯的地方請大家多多包涵,歡迎在評論下留言指正~

一、什么是ThreadLocal

聲明:本文使用的是JDK 1.8

首先我們來看一下JDK的文檔介紹:


/*** This class provides thread-local variables.  These variables differ from* their normal counterparts in that each thread that accesses one (via its* {@code get} or {@code set} method) has its own, independently initialized* copy of the variable.  {@code ThreadLocal} instances are typically private* static fields in classes that wish to associate state with a thread (e.g.,* a user ID or Transaction ID).* * <p>For example, the class below generates unique identifiers local to each* thread.* A thread's id is assigned the first time it invokes {@code ThreadId.get()}* and remains unchanged on subsequent calls.*/     

結合我的總結可以這樣理解:ThreadLocal提供了線程的局部變量,每個線程都可以通過set()get()來對這個局部變量進行操作,但不會和其他線程的局部變量進行沖突,實現了線程的數據隔離~。

簡要言之:往ThreadLocal中填充的變量屬于當前線程,該變量對其他線程而言是隔離的。

二、為什么要學習ThreadLocal?

從上面可以得出:ThreadLocal可以讓我們擁有當前線程的變量,那這個作用有什么用呢???

2.1管理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的實現是更強大的)~

2.2避免一些參數傳遞

避免一些參數的傳遞的理解可以參考一下Cookie和Session:

  • 每當我訪問一個頁面的時候,瀏覽器都會幫我們從硬盤中找到對應的Cookie發送過去。
  • 瀏覽器是十分聰明的,不會發送別的網站的Cookie過去,只帶當前網站發布過來的Cookie過去

瀏覽器就相當于我們的ThreadLocal,它僅僅會發送我們當前瀏覽器存在的Cookie(ThreadLocal的局部變量),不同的瀏覽器對Cookie是隔離的(Chrome,Opera,IE的Cookie是隔離的【在Chrome登陸了,在IE你也得重新登陸】),同樣地:線程之間ThreadLocal變量也是隔離的....

那上面避免了參數的傳遞了嗎??其實是避免了。Cookie并不是我們手動傳遞過去的,并不需要寫<input name= cookie/>來進行傳遞參數...

在編寫程序中也是一樣的:日常中我們要去辦理業務可能會有很多地方用到身份證,各類證件,每次我們都要掏出來很麻煩

// 咨詢時要用身份證,學生證,房產證等等....public void consult(IdCard idCard,StudentCard studentCard,HourseCard hourseCard){}// 辦理時還要用身份證,學生證,房產證等等....public void manage(IdCard idCard,StudentCard studentCard,HourseCard hourseCard) {}//......

而如果用了ThreadLocal的話,ThreadLocal就相當于一個機構,ThreadLocal機構做了記錄你有那么多張證件。用到的時候就不用自己掏了,問機構拿就可以了。

在咨詢時的時候就告訴機構:來,把我的身份證、房產證、學生證通通給他。在辦理時又告訴機構:來,把我的身份證、房產證、學生證通通給他。...

// 咨詢時要用身份證,學生證,房產證等等....public void consult(){threadLocal.get();}// 辦理時還要用身份證,學生證,房產證等等....public void takePlane() {threadLocal.get();}

這樣是不是比自己掏方便多了。

當然了,ThreadLocal可能還會有其他更好的作用,如果知道的同學可在評論留言哦~~~

三、ThreadLocal實現的原理

想要更好地去理解ThreadLocal,那就得翻翻它是怎么實現的了~~~

聲明:本文使用的是JDK 1.8

首先,我們來看一下ThreadLocal的set()方法,因為我們一般使用都是new完對象,就往里邊set對象了

public void set(T value) {// 得到當前線程對象Thread t = Thread.currentThread();// 這里獲取ThreadLocalMapThreadLocalMap map = getMap(t);// 如果map存在,則將ThreadLocal作為key,要存儲的對象作為value存到map里面去if (map != null)map.set(this, value);elsecreateMap(t, value);}

上面有個ThreadLocalMap,我們去看看這是什么?


static class ThreadLocalMap {/*** 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;}}//....很長
}

通過上面我們可以發現的是ThreadLocalMap是ThreadLocal的一個內部類。用Entry類來進行存儲

我們的值都是存儲到這個Map上的,key是當前ThreadLocal對象

如果該Map不存在,則初始化一個:

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}

如果該Map存在,則從Thread中獲取

/*** Get the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param  t the current thread* @return the map*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

Thread維護了ThreadLocalMap變量

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

從上面又可以看出,ThreadLocalMap是在ThreadLocal中使用內部類來編寫的,但對象的引用是在Thread中

于是我們可以總結出:Thread為每個線程維護了ThreadLocalMap這么一個Map,而ThreadLocalMap的key是LocalThread對象本身,value則是要存儲的對象

有了上面的基礎,我們看get()方法就一點都不難理解了:

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

3.1ThreadLocal原理總結

  1. 每個Thread維護著一個ThreadLocalMap的引用
  2. ThreadLocalMap是ThreadLocal的內部類,用Entry來進行存儲
  3. 調用ThreadLocal的set()方法時,實際上就是往ThreadLocalMap設置值,key是ThreadLocal對象,值是傳遞進來的對象
  4. 調用ThreadLocal的get()方法時,實際上就是往ThreadLocalMap獲取值,key是ThreadLocal對象
  5. ThreadLocal本身并不存儲值,它只是作為一個key來讓線程從ThreadLocalMap獲取value

正因為這個原理,所以ThreadLocal能夠實現“數據隔離”,獲取當前線程的局部變量值,不受其他線程影響~

四、避免內存泄露

我們來看一下ThreadLocal的對象關系引用圖:

img_8675b2694b5a4b386b78d7aa58ef6652.jpe

ThreadLocal內存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一樣長,如果沒有手動刪除對應key就會導致內存泄漏,而不是因為弱引用

想要避免內存泄露就要手動remove()掉

五、總結

ThreadLocal這方面的博文真的是數不勝數,隨便一搜就很多很多~站在前人的肩膀上總結了這篇博文~

最后要記住的是:ThreadLocal設計的目的就是為了能夠在當前線程中有屬于自己的變量,并不是為了解決并發或者共享變量的問題

如果看得不夠過癮,覺得不夠深入的同學可參考下面的鏈接,很多的博主還開展了一些擴展知識,我就不一一展開了~

參考博文:

  • http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/
  • https://www.cnblogs.com/zhangjk1993/archive/2017/03/29/6641745.html#_label2
  • http://www.cnblogs.com/dolphin0520/p/3920407.html
  • http://www.cnblogs.com/dolphin0520/p/3920407.html
  • http://www.iteye.com/topic/103804
  • https://www.cnblogs.com/xzwblog/p/7227509.html
  • https://blog.csdn.net/u012834750/article/details/71646700
  • https://blog.csdn.net/winwill2012/article/details/71625570
  • https://juejin.im/post/5a64a581f265da3e3b7aa02d

img_b9281e41ca8ec4a8c7abeb39bd7a8fef.jpe

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關注微信公眾號:Java3y

更多的文章可往:文章的目錄導航

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

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

相關文章

sublime python配置運行

1、安裝python環境 安裝完成時&#xff0c;WinR → 輸入cmd → Enter → 調出來命令行&#xff0c;輸入python確認安裝是否成功。 2、安裝sublime 3、打開sublime&#xff0c;選擇工具——編譯系統——新建編譯系統&#xff0c;復制粘貼下面文件。保存為python3,選擇工具——編…

Linux 終端環境安裝 L2TP 客戶端

安裝&#xff1a; yum -y install xl2tpd ppp 安裝成功后&#xff0c;直接進入配置流程 配置&#xff1a; 配置過程也并不復雜&#xff0c;主要有兩個文件。首先就是配置 /etc/xl2tpd/xl2tpd.conf 文件。此文件原有的內容是做服務端用的&#xff0c;而作為客戶端使用只需保留如…

如何在Twitch上設置捐款

Many people on Twitch stream as a hobby. If you’re thinking about going full-time, though, you’ll need to raise some cash. Setting up donations on Twitch is one way you can do it! Twitch上的許多人都將其作為愛好。 但是&#xff0c;如果您打算全職工作&#x…

JAVA-Concurrency之CountDownLatch說明

2019獨角獸企業重金招聘Python工程師標準>>> As per java docs, CountDownLatch is a synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes. CountDownLatch concept is very comm…

windows下共享文件夾在Linux下打開

①首先在Windows下創建一個準備用來共享用的文件夾 ②在虛擬機下選擇第一步創建的文件夾為共享文件夾 ③在虛擬機shell命令框下輸入 cd /mnt/sgfs 回車進入共享文件夾。 備注&#xff1a;其他細節與配圖后續再更新 Thanks轉載于:https://www.cnblogs.com/better-day/p/105026…

設置微軟應用商店的代理_如何設置和使用Microsoft家庭安全應用

設置微軟應用商店的代理The Microsoft Family Safety app provides a set of reporting and parental control tools for people with Microsoft accounts. With filtering controls, location reporting, and app-usage recording, this app gives parents a way to monitor t…

Linux跨平臺遠程控制

轉載于:https://blog.51cto.com/13660858/2094987

20189222 《網絡攻防實踐》第二周作業

20189222《網絡攻防實踐》第2周學習總結 教材學習內容總結 第一章&#xff1a; 要點1&#xff1a;分清黑客與駭客&#xff0c;提倡在掌握技術的同時&#xff0c;還要遵循黑客道德與法律法規。 要點2&#xff1a;網絡攻防的主要內容包括系統安全攻防、網絡安全攻防、物理攻擊和社…

Zoom Host可以真正看到您的所有私人消息嗎?

Girts Ragelis/Shutterstock.comGirts Ragelis / Shutterstock.comViral social media posts are alleging that Zoom’s private messages aren’t really private—if you’re chatting privately during a Zoom meeting, the host can see your entire conversation. Is tha…

使用Keras進行深度學習:(三)使用text-CNN處理自然語言(上)

歡迎大家關注我們的網站和系列教程&#xff1a;http://www.tensorflownews.com/&#xff0c;學習更多的機器學習、深度學習的知識&#xff01; 上一篇文章中一直圍繞著CNN處理圖像數據進行講解&#xff0c;而CNN除了處理圖像數據之外&#xff0c;還適用于文本分類。CNN模型首次…

powerpoint轉換器_如何將PowerPoint演示文稿轉換為主題演講

powerpoint轉換器If someone sends you a Microsoft PowerPoint presentation, but you’d rather use Apple’s presentation software, Keynote, you’re in luck! Apple’s done all the hard work for you. Here’s how to convert a PowerPoint presentation to Keynote. …

Android高仿大眾點評(帶服務端)

2019獨角獸企業重金招聘Python工程師標準>>> 實例講解了一個類似大眾點評的項目&#xff0c;項目包含服務端和android端源碼, 服務端為php代碼&#xff0c;如果沒有接觸過php, 文章中講解一鍵部署php的方法&#xff0c;讓您5分鐘將服務端搭建成功, 您也可以將php換成…

vista任務欄透明_在Windows XP中獲取Vista任務欄縮略圖預覽

vista任務欄透明It was only a matter of time before people started cloning Windows Vista features and adding them into Windows XP. One of my favorite Vista features is the thumbnails that popup when you mouse over the taskbar. And now I can use them in XP a…

Spring實戰Day2

創建對象之后如何體現對象之間的依賴&#xff1f; Spring容器負責創建Bean和依賴注入&#xff0c;那么Spring是怎么將Bean裝配在一起的呢&#xff1f; Spring提供了三種方式裝配機制 1.隱式的bean發現機制和自動裝配 圖一圖二&#xff0c;是兩個組件與Config類同包 圖三&#x…

Git的狀態轉換

近期公司用Git來管理代碼&#xff0c;用起來是要比svn爽一些。就是剛接觸的時候比較痛苦&#xff0c;特別是那些狀態(版本號的提交/合并/回退)。差點把我搞暈了。如今回過頭來總結一下&#xff0c;就清楚多了。 就本地倉庫來看。Git能夠分成5個不同的狀態。能夠通過$ git statu…

RN自定義組件封裝 - 播放類似PPT動畫

1. 前言 近日&#xff0c;被安排做一個開場動畫的任務。雖然RN提供了Animated來自定義動畫&#xff0c;但是本次動畫中的元素頗多&#xff0c;交互甚煩。。。在完成任務的同時&#xff0c;發現很多步驟其實是重復的&#xff0c;于是封裝了一個小組件記錄一下&#xff0c;分享給…

target存放的是編譯后的.class文件地方 默認情況下不會講非class文件放入進入 如果要使用非.class文件 需要通過增加配置方式自動加入文件...

target存放的是編譯后的.class文件地方 默認情況下不會講非class文件放入進入 如果要使用非.class文件 需要通過增加配置方式自動加入文件轉載于:https://www.cnblogs.com/classmethond/p/10520615.html

dropbox mac_如何在Windows或Mac上啟動時阻止Dropbox打開

dropbox macDropbox is a handy way to synchronize files across devices via the cloud. By default, Dropbox starts whenever you turn on your Windows PC or Mac, but sometimes you might not want it to. Here’s how to make sure it doesn’t launch when you startu…

spring cloud 總結

Finchley版本Spring Cloud Finchley; Spring Boot 2.0.3 史上最簡單的 SpringCloud 教程 | 第一篇: 服務的注冊與發現&#xff08;Eureka&#xff09;(Finchley版本) 所有的服務向這里注冊 史上最簡單的SpringCloud教程 | 第二篇: 服務消費者&#xff08;restribbon&#xff09…

深入分析 ThreadLocal 內存泄漏問題

2019獨角獸企業重金招聘Python工程師標準>>> ThreadLocal 的作用是提供線程內的局部變量&#xff0c;這種變量在線程的生命周期內起作用&#xff0c;減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的復雜度。但是如果濫用 ThreadLocal&#xff0c;就可能會…