2009-09-29 24 views

回答

71

線程是一個執行單元,因此多個線程可以同時執行相同的代碼。如果多個線程同時在對象/實例上執行,它們將共享實例變量。每個線程都有自己的局部變量,但很難在不傳遞參數的情況下在對象之間共享這些變量。

最好通過一個例子來解釋。假設您有一個獲取登錄用戶的Servlet,然後執行一些代碼。

doGet(HttpServletRequest req, HttpServletResponse resp) { 
    User user = getLoggedInUser(req); 
    doSomething() 
    doSomethingElse() 
    renderResponse(resp) 
} 

現在如果doSomething()方法需要訪問用戶對象,會發生什麼?您不能使用戶對象成爲實例或靜態變量,因爲每個線程都會使用相同的用戶對象。你可以通過周圍的用戶對象作爲參數,但這很快就變成混亂和泄露用戶對象到每一個方法調用:

doGet(HttpServletRequest req, HttpServletResponse resp) { 
    User user = getLoggedInUser(req); 
    doSomething(user) 
    doSomethingElse(user) 
    renderResponse(resp,user) 
} 

一個更優雅的解決方案是將用戶對象到一個ThreadLocal

doGet(HttpServletRequest req, HttpServletResponse resp) { 
    User user = getLoggedInUser(req); 
    StaticClass.getThreadLocal().set(user) 
    try { 
    doSomething() 
    doSomethingElse() 
    renderResponse(resp) 
    } 
    finally { 
    StaticClass.getThreadLocal().remove() 
    } 
} 

現在需要的用戶對象在任何時候都可以通過從線程局部提取它弄個任何代碼,而無需求助於那些討厭的額外的參數:

User user = StaticClass.getThreadLocal().get() 

如果使用此方法,請注意在finally塊中再次刪除對象。否則,用戶對象可能會在使用線程池的環境中(如Tomcat應用程序服務器)停留。

編輯:靜態類的代碼

class StaticClass { 
    static private ThreadLocal threadLocal = new ThreadLocal<User>(); 

    static ThreadLocal<User> getThreadLocal() { 
    return threadLocal; 
    } 
} 
+1

什麼是靜態類在這裏?擴展Thread的類? – Ajay 2009-09-29 08:38:53

+0

StaticClass只是獲取ThreadLocal對象的方便方法。我將編輯示例以包含StaticClass的代碼。 – leonm 2009-09-29 09:01:35

+7

我不會說它總是更優雅,但它更方便 – 2009-09-29 11:48:42

7

線程對象可以有內部數據成員,但任何擁有(或可以獲得)對該線程對象的引用的人都可以訪問它們。一個ThreadLocal故意只與每個訪問它的線程相關聯。好處是沒有併發問題(在ThreadLocal的上下文中)。一個線程的內部數據成員具有所有共享狀態所具有的所有相同的併發問題。

讓我解釋將結果與特定線程關聯的想法。一個ThreadLocal的本質是這樣的:

public class MyLocal<T> { 
    private final Map<Thread, T> values = new HashMap<Thread, T>(); 

    public T get() { 
    return values.get(Thread.currentThread()); 
    } 

    public void set(T t) { 
    values.put(Thread.currentThread(), t); 
    } 
} 

現在有它的不止這些,但你可以看到返回的值是由當前線程確定。這就是爲什麼它是本地到每個線程。

+0

故意與每個線程相關聯的含義?線程本身是一個對象嗎?所以與一個線程關聯的其實就意味着與線程對象關聯? – Ajay 2009-09-29 06:50:26

+0

@Ajay:是的。 ThreadLocal實例通過連接到Thread對象的半私有數據結構與Thread對象關聯;看到「包私人」成員Thread.threadLocals和Thread.inherittableThreadLocals – 2009-09-29 06:58:12

+0

@Stephen:那麼有什麼區別是有一個threadLocal相比,有一個線程類的成員。 – Ajay 2009-09-29 07:08:26

1

ThreadLocals的優點在於,它們可以在簡單的香草線程或Thread的任何子類上運行的方法使用。

相比之下,如果您的線程本地語言必須作爲線程的自定義子類的成員來實現,則有很多事情您不能做。例如,如果需要在預先存在的vanilla線程實例上運行方法,您的應用程序將遇到麻煩;即由應用程序編寫者沒有編寫且不能修改的某些庫代碼創建的實例。

5

ThreadLocal的是在web應用中非常有用。典型的模式是,在Web請求處理的開始處(通常在servlet過濾器中)狀態存儲在ThreadLocal變量中。由於請求的所有處理都在1個線程中完成,所有參與請求的組件都可以訪問此變量。

2

這個問題區有一個維基百科entry about。在我們的環境中,它通常用於保持本地請求。在服務器端,請求主要由單個線程處理。爲了保存本地信息,您可以輸入數據,例如會話數據,在一個線程局部變量中。這些數據對其他請求(線程)是不可見的,因此您不需要將其與其他請求同步。

並且不要忘記,有JAVA API構造,即而不是線程安全,例如, DateFormat。 DateFormat的靜態實例在服務器端不起作用。

事實上,當您使用自己的私人數據副本而不是使用鎖和顯示器進行處理時,處理多線程編程會更容易。

10

你必須認識到,繼承Thread類的一個實例是同樣的事,作爲一個實際的Java線程(可以被想象成是通過您的代碼運行,併成爲一個「執行的指針」 )。這樣一類

實例表示 Java線程,並允許對其進行操作(如中斷的話),但除此之外,他們只是普通的對象,其成員可以從所有的線程,可以弄個訪問對該對象的引用(即not hard)。

當然,您可以嘗試保持一個成員私有,並確保它僅用於run()或從中調用的方法(公共方法也可以從其他線程調用),但這很容易出錯,並且不會真正可行的是一個更復雜的系統,你不想將數據全部保存在一個Thread子類中(實際上你不應該繼承Thread,而是使用Runnable)。

ThreadLocal是一種簡單,靈活的方式,可以讓其他線程同時訪問每線程數據,而不需要很大的努力或設計折衷。