ThreadLocal可以解決併發問題嗎?

前言

到底什麼是執行緒的不安全?為什麼會存線上程的不安全?執行緒的不安全其實就是多個執行緒併發的去操作同一共享變數沒用做同步所產生意料之外的結果。那是如何體現出來的呢?我們看下面的一個非常經典的例子:兩個操作員同時操作同一個銀行賬戶,A操作員存錢,100B操作員取錢50。我們看一下流程。

ThreadLocal可以解決併發問題嗎?

兩個操作員同時處理,沒用做同步這個時候我們發現銀行賬戶最終餘額剩餘950元,在我們想的最終結果銀行賬戶應該剩餘1000+100-50=1050元,在執行過程中我們沒有加鎖,最終導致了執行結果偏離預期。那麼如何解決的?一般的解決措施就是加鎖,加同步鎖所以這就需要使用者一定要知道鎖是什麼。我們來看一下加鎖之後的效果是不是我們所預期的。

ThreadLocal可以解決併發問題嗎?

在新增同步鎖後我們可以看到,A操作員和B操作員同時去操作賬戶,但是A先搶佔到資源,所以B就只能等待A操作員釋放鎖才能去操作銀行賬戶,那麼最終結果是我們所預期的嗎?答案是的。

同步的話一般都是加鎖,如果現在我想建立多個執行緒每個執行緒都是訪問的自己的變數呢?各個執行緒之間毫無關聯?

答案是有的。

ThreadLocal問題

ThreadLocal 是JDK提供的,它提供了執行緒本地變數。什麼是執行緒本地變數呢?其實就是你建立了一個Threadlocal變數,每個訪問Threadlocal變數的執行緒都有一個本地副本。我們看下面的圖:

ThreadLocal可以解決併發問題嗎?

從上面看出你建立一個ThreadLocal變數,每個訪問該的執行緒都會複製到自己的本地,所以執行緒操作的都是本地的副本,這也就是說每個執行緒都是操作的自己本地的變數,那就完美的避免了執行緒安全的問題。

在這裡還有一個問題。我在寫這篇文章的時候看過很多文章,總的來說就是ThreadLocal就是為了解決多執行緒併發問題而提供的一種方法,還有一種解釋就是ThreadLocal的最終目的就是為了解決多執行緒訪問共享資源所產生的。真的對嗎?ThreadLocal並沒有共享那麼從何而來的同步呢?

自己的想法

在看了Java併發程式設計之美后我所理解的Threadlocal提供了執行緒本地變數的副本,每個執行緒實際操作的時自己本地的變數副本,也就是說該變數副本只能當前執行緒訪問,就不存在多個執行緒共享的問題,從Threadlocal名字我們也能看出本地執行緒。那那那它也就不存在去解決併發問題了。

如何使用

我們來看下面的例子。

ThreadLocal可以解決併發問題嗎?

輸出結果:

Thread[Thread-1,5,main]====57

Thread[Thread-0,5,main]====75

建立了兩個執行緒,它們都在threadlocal上面都set了一個隨機數,我們看最後得輸出結果每個都是不同得值,那麼我們如果把threadlocal替換成一個集合會發生什麼,由於兩個執行緒時上個執行緒生成的隨機數57會被第二個執行緒覆蓋掉,而在Threadlocal中兩個執行緒都是操作的自己的本地副本,那麼兩個執行緒互不影響都無法操控到對方的資料,因此它們存取的都是不同的值。

實現原理

那麼Threadlocal是如何實現的呢?在研究Threadlocal的實現原理我們先看一下Thread的內部屬性。

ThreadLocal可以解決併發問題嗎?

threadLocals 此執行緒儲存的Threadlocal的值

inheritableThreadLocals等到後面再說。

在Thread的內部屬性中我們看到了這兩個預設為null的屬性,threadLocals用來儲存Threadlocal的本地副本,預設是為null只有呼叫Threadlocal的set時才會建立。也就是說Threadlocal就類似一個工具,它的作用就是把value的值透過set存線上程每個執行緒的threadLocals 中,只要執行緒一直存在threadLocals 也就一直存在。所以當不需要使用本地變數的時候可以呼叫Threadlocal的remove來清空本地變數。而threadLocals 為什麼繼承魚ThreadLocalMap呢?ThreadLocalMap是一個定製的HashMap,而使用Map的原因就是可以每個執行緒關聯多個Threadlocal變數。

set方法

我們來看一下set方法是如何實現的。

ThreadLocal可以解決併發問題嗎?

可以看出流程非常簡單,首先獲取當前執行緒然後在進行下一步操作,我們在看一下getMap做了什麼

ThreadLocal可以解決併發問題嗎?

getMap主要就是返回了當前threadLocals的屬性。那如果map為空呢?

ThreadLocal可以解決併發問題嗎?

如果map為空的話就直接建立一個新的ThreadLocalMap。

我們來看一下流程圖。

ThreadLocal可以解決併發問題嗎?

get方法

看一下Get方法

ThreadLocal可以解決併發問題嗎?

首先根據當前執行緒獲取例項如果存在就返回,如果不存在就先初始化一個空值,然後判斷如果當前threadLoacals不為空就直接set一個空,否則就建立一個變數。

remove方法

ThreadLocal可以解決併發問題嗎?

remove方法相對來說比較簡單。

總結

Threadlocal的實現原理其實就是透過set把value set到執行緒的threadlocals屬性中,threadlocals型別是Map其中的Key就是Threadlocal的this引用,value就是我們所set的值,如果當前執行緒不銷燬的話threadlocals會一直存在。一直存在的話可能會造成記憶體溢位,所以使用完之後儘量remove一下。不過在這裡又有一個問題那就是如果我的執行緒想要讀取主執行緒的變數要怎麼做?我們上面的例子都是設定的新建立的執行緒,那麼現在我在主執行緒中set一個值,這個時候我在新建立的執行緒中可以讀取到嗎?答案是不可以,因為Threadlocal不支援繼承性。

我們看下面的例子:

ThreadLocal可以解決併發問題嗎?

輸出結果:

Thread[Thread-0,5,main]====null

也就是說Threadlocal不支援繼承性,主執行緒設定了值,在子執行緒中是獲取不到的。那我現在想要獲取主執行緒裡面的值要怎麼做?

Threadlocal是實現不了的,不過Threadlocal有一個子類可以實現。InheritableThreadLocal,InheritableThreadLocal是Threadlocal的實現,我們來看一個簡單的例子。

ThreadLocal可以解決併發問題嗎?

輸出結果:

Thread[Thread-0,5,main]====1000

執行結果發現子執行緒是可以獲取到主執行緒設定的值的,那它是如何實現的?

我們看一下程式碼實現:

ThreadLocal可以解決併發問題嗎?

InheritableThreadLocal是繼承Threadlocal的,並且把threadlocals給替換成inheritableThreadLocals了所以上面的inheritableThreadLocals我要留在最後說,那麼替換成inheritableThreadLocals後子執行緒就可以獲取到主執行緒設定的屬性了嗎?我們在看一下Thread的實現。

ThreadLocal可以解決併發問題嗎?

看Thread的初始化方法可以看出,先獲取了當前執行緒(主執行緒)判斷主執行緒的inheritableThreadLocals不為空的話就呼叫createInheritedMap方法賦值給子執行緒中的inheritableThreadLocals。具體這裡解釋太多。有機會在寫一篇文章來解釋。

本文完