2015-05-08 18 views
5

ThreadLocal確保字段對於線程是全局的和本地的。 (全局的,因爲它可用於線程和本地的所有方法,因爲它僅限於該線程的堆棧)Java的Threadlocal可以應用於非靜態字段嗎?如果是,如何?

這對我來說沒什麼意義,因爲每個線程的堆棧都僅限於該線程。所以它已經是'threadlocal'了,對吧?

爲什麼我們需要ThreadLocal? - 在進一步閱讀時,我確認了來自各個網站的假設(其中大多數網站未能提供這些事實或相互矛盾),這確實適用於靜態字段。 這確實有道理。

所以我的問題是,有沒有一個多線程場景的ThreadLocal可以/需要應用於非靜態字段? (我碰到一些網站,說出來「的ThreadLocal」是用於靜態字段「主要」;甚至https://docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html使用單詞「通常」)

+1

是,ThreadLocals的情況下正常工作。然而,本地線程的「優勢」在於,您可以輕鬆訪問它。當你不把它放到一個靜態字段中時,你將失去這個優勢,因爲你必須交出實際的持有者實例。它有助於將ThreadLocal視爲一個HashMap,其中Thread.currentThread()是關鍵。 – eckes

+1

我認爲這個「全球」一詞顯然是錯誤的。不要打擾它。有足夠的樣本明確表示您不想讓線程(寫入)訪問它,例如安全或事務上下文。這通常是使用包或專用可見字段來完成的。 – eckes

回答

3

只有本地變量是在線程的堆棧*靜態變量。實例變量都存在於堆上。如果我們想要的話,我們也可以僅僅通過一個ThreadLocal,而不會在物體或類中生活。

我們可以查看ThreadLocal作爲局部變量這可能是在任何時間點訪問。

正常局部變量在聲明範圍時被銷燬,但ThreadLocal可以在任何地方生存。

所以我的問題是,有沒有一個多線程場景的ThreadLocal可以/需要應用於非靜態字段?

我們可以一起來......

interface Dial {} 

class Gadget { 
    ThreadLocal<Dial> d = new ThreadLocal<>(); 
} 

class Gizmo implements Runnable { 
    Gadget g; 
    Gizmo(Gadget g) { 
     this.g = g; 
    } 
    public void run() {} 
} 

{ 
    Gadget g = new Gadget(); 
    new Thread(new Gizmo(g)).start(); 
    new Thread(new Gizmo(g)).start(); 
} 

兩個線程共享的Gadget相同的實例,但都有自己的本地Dial

爲什麼我們需要ThreadLocal?

事實是,我們並不需要ThreadLocal,如果有的話。


*只有局部變量是在線程堆棧除了在對象可以堆棧中分配的理論優化the JVM is allowed to do的情況。如果發生這種情況,我們永遠不會知道這件事,因爲它不會改變程序的行爲。如果一個對象在線程之間共享,它就在堆上。

+0

感謝您的明確解釋。讓我們來看看我是否理解這個權利 - 所以泄漏發生的主要原因是,一個未被銷燬的ThreadLocal坐在堆上(並且受GC控制) - 這與其他對象有什麼不同? – killjoy

+0

我不知道泄漏是你的問題的一部分,所以我的答案沒有解決這個問題。使用ThreadLocal的內存泄漏取決於應用程序保留對未使用的線程的引用,例如來自例如來自例如程序的線程。一個線程池。保持對線程的引用是導致泄漏的原因,而不是保留對ThreadLocal的引用。 – Radiodef

+1

對不起應該更清楚 - 我得到TLs的概念導致內存泄漏,如果他們不從其他答案銷燬。我只是認爲我會評論你的答案,因爲你已經提到TL會坐在堆上,但同樣你也回答了這個問題,所以我會將其標記爲已接受。謝謝 ! – killjoy

2

Threadlocal是一組變量的容器,每個變量都只對一個線程可用,即它爲每個線程提供了一個包含的類的實例。因此,ThreadLocal對象是全局可用的(花費在權限上),但包含的類的每個實例僅在本地可用於該線程。

這意味着它需要爲每個線程稍微不同地進行初始化和銷燬​​,但在所有其他方面它與其他變量相同。

注意:銷燬實例的目的是爲了防止線程從池中被抽取時在同一線程上的獨立調用之間的內存泄漏或狀態泄漏。

線程局部變量通常用於爲不需要線程間通信的存儲提供線程安全選項。一個這樣的例子是在CDI中定義一個新的範圍,該範圍將完全存在於一個線程中(例如,您可以定義一個異步請求範圍)。

因此,只要在需要的地方訪問,就不需要將其限制爲靜態或非靜態變量。在默認的java環境中靜態變量是提供這種訪問的一種便利方式,但在CDI中,Singleton bean可以提供相同級別的訪問並具有許多優點。

在注入的單例情況下,單例bean將包含對ThreadLocal的非靜態引用並提供服務以訪問其中包含的實例。注入單例的引用和單例對ThreadLocal的引用都是非靜態的。

+0

線程本地人與「包含類的實例」無關(不管那是:)。甚至連公共靜態在Java中都不是全局的,所以ThreadLocal如何成爲... – eckes

+1

ThreadLocal是一組對象的容器,每個對象只能用於一個線程。我所說的包含類是指由該集合中的對象實例化的類。爲什麼每個人都對全局變量理論和靜態變量之間的差異感到如此垂頭喪氣。問題是threadlocal可以是非靜態的。我編輯了我的答案來澄清這一點(我希望) – redge

+0

我也對此感到困惑,這個問題不是關於範圍,而是爲什麼只有靜力學似乎通常與TLs一起使用。 – killjoy

6

我認爲這個問題是基於一個錯誤的前提。

ThreadLocal確保字段對於線程是全局的和本地的。 (全局,因爲它可用於線程和本地中的所有方法,因爲它僅限於該線程的堆棧)

這不是「全局」的真正含義。全球確實意味着整個計劃無需任何資格即可訪問。事實上,Java沒有真正的全局變量。它最接近的是「公共靜態」字段......可以通過限定條件訪問。

但回到問題...

一個ThreadLocal 變量如果兩個條件都滿足可用的方法:

  • 的方法調用必須在正確的線程。 (如果它在不同的線程上,它看到不同的變量。)

  • 該方法必須能夠獲得有效「聲明」線程局部變量(在任何線程中)的ThreadLocal對象。

第二個條件是否意味着「聲明」是全局的?

IMO,no。有兩個原因。

  • 傳統意義上的全局變量只有一個實例。本地線程對每個線程都有一個不同的實例。

  • ThreadLocal可通過特定方法訪問的事實不會使其可以被任何方法訪問。ThreadLocal的正常(和良好實踐)使用模式是將對象引用保存在private static變量中。這意味着同一類中的方法可以使用相應的線程局部變量實例...但其他類中的方法不能。

現在可以放在一個public static變量引用ThreadLocal,但爲什麼要這麼做?您正在明確地創建一個泄漏抽象 ......這可能會導致問題。

當然,你可以做什麼@ Radiodef的回答顯示;即創建一個本地線程,其實例只能從特定類的特定實例上的方法訪問。但是很難理解你爲什麼想要/需要進入這種限制水平。 (它是容易導致存儲泄漏...)


簡短的回答:如果你不想讓你的線程局部變量進行訪問,限制訪問ThreadLocal對象。

+0

@redge - 我在靜態Java中使用靜態關鍵字。設置「靜態」值的機制是正交的。但是,如果您建議我們應該使用DI注入'ThreadLocal'對象...我看不到這一點。 –

+0

如果您將「全局訪問」等同於「全局變量」......您誤解了「全局變量」實際上是什麼。 –

+0

我不相信這一點。 1)DI和單身人士都有自己的位置,但是IMO在這方面提供了零實用益處*。如果你真的認爲他們這麼做,請寫下你自己的答案以正確解釋你自己。 2)我的回答是不推薦全局變量。它實際上解釋了線程局部變量不是全局變量。 –

2
static ThreadLocal<Whatever> threadLocal; 

聲明指向類ThreadLocal的實例的字段。每個線程都有一個Map<ThreadLocal<?>, Object>說的ThreadLocal實例與相應的「線程局部變量」有這個Thread值相關聯。也就是說,每個ThreadLocal例如標識「線程局部變量」,並儘可能的JVM而言,ThreadLocal是像任何其他類。

通常,當我們需要一個「線程局部變量」時,我們只需要一個,因此只創建一個單一的ThreadLocal實例,它通常保存在一個靜態字段中以方便訪問。

如果我們需要爲主機對象的每個實例的新「線程局部變量」,我們可以簡單地創建爲每個主機對象的新ThreadLocal實例,並在非靜態字段存儲。對此,我想不出這是最簡單的解決方案,但我們可以做到。

+0

我最近用Java ee6做了大量的工作。在那段時間裏,我認爲在大多數情況下,在大多數情況下,注入的單例作用域對象比靜態引用要好得多,並且對服務提供相同的訪問級別。對一個公共變量的這種類型的訪問是非局部線程本地提供了一個很好的解決方案的情況。 – redge

相關問題