2016-05-19 35 views
4

在下面的單例作用域服務類中,類中的所有方法都需要調用Service.doA()時已知的某個用戶上下文。不是在方法中傳遞信息,而是考慮將這些值存儲在TheadLocal中。我有兩個關於這種方法的問題:有關在Spring單例作用域服務中使用ThreadLocal的問題

1)下面的實現是否正確使用ThreadLocal?也就是說,它是線程安全的,正確的值將被讀取/寫入ThreadLocal

2)是否需要明確清理ThreadLocal userInfo以防止任何內存泄漏?它會被垃圾收集?

@Service 
public class Service { 
    private static final ThreadLocal<UserInfo> userInfo = new ThreadLocal<>(); 

    public void doA() { 
     // finds user info 
     userInfo.set(new UserInfo(userId, name)); 
     doB(); 
     doC(); 
    } 

    private void doB() { 
     // needs user info 
     UserInfo userInfo = userInfo.get(); 
    } 

    private void doC() { 
     // needs user info 
     UserInfo userInfo = userInfo.get(); 
    } 
} 
+0

如果您使用的是singleton bean,只需將該字段設置爲實例字段即可。 –

+0

@SotiriosDelimanolis但是有一個UserInfo實例字段不是線程安全的,不是? – Glide

+0

無論是實例字段還是類字段都不會改變其線程安全性。此外,它不是'UserInfo'字段,它是'ThreadLocal'字段。 'ThreadLocal'是線程安全的。而這個領域將是'最後的'。 –

回答

5

1)的示例代碼是好的,除了在出生日期和DOC名稱衝突你使用相同的名稱爲靜態變量引用的ThreadLocal爲你在哪裏是用於保存從ThreadLocal中提取的內容的局部變量。

2)在ThreadLocal中存儲的對象保持連接到該線程,直到顯式刪除。例如,如果您的服務在servlet容器中執行,則當請求完成時,它的線程將返回到池中。如果你還沒有清理線程的ThreadLocal變量內容,那麼這些數據將會繼續保留,以便隨後爲線程分配的任何請求。每個線程都是一個GC根,連接到該線程的threadlocal變量在線程死後纔會被垃圾收集。據the API doc:只要

每個線程擁有它的線程局部變量副本的隱式引用,只要線程是活動的並且ThreadLocal實例是可訪問的;在線程消失後,線程本地實例的所有副本都將進行垃圾回收(除非存在對這些副本的其他引用)。

如果您的上下文信息僅限於一個服務的範圍,則最好通過參數傳遞信息而不是使用ThreadLocal。 ThreadLocal適用於需要跨不同服務或不同層次提供信息的情況,似乎只有在您的代碼只會被一個服務使用時纔會過度複雜化。現在,如果您有不同的不同對象上的AOP建議將使用的數據,將這些數據放入threadlocal可能是一種有效的用法。

要執行清理,通常需要確定線程在當前處理完成時的位置,例如在servlet篩選器中,其中可以在線程返回到線程池之前移除threadlocal變量。你不會使用try-finally塊,因爲你插入threadlocal對象的地方遠不及你正在清理它的地方。

0

使用後一定要清理乾淨。 ThreadLocals非常容易泄漏內存,通過classloders泄漏堆內存和permgen/metaspace內存。在你的情況下,最好的辦法是:

public void doA() { 
    // finds user info 
    userInfo.set(new UserInfo(userId, name)); 
    try { 
    doB(); 
    doC(); 
    } finally { 
    userInfo.remove() 
    } 
} 
1

當您使用ThreadLocal,你需要確保你清理無論發生什麼事,因爲:

  1. 它創建莫名其妙內存泄漏的價值不能被GC收集,因爲對象符合GC當且僅當沒有對象直接或間接具有對該對象的直接或間接引用。所以,在這裏例如,您ThreadLocal實例具有間接的硬引用到自己的價值,雖然其內部ThreadLocalMap,擺脫的唯一途徑這個硬引用是調用ThreadLocalMap#remove(),因爲它會從ThreadLocalMap刪除值。另一個潛在的方法,使你的價值資格GC會出現這種情況在您ThreadLocal實例本身資格GC,但在這裏它是在類Service恆定的,因此永遠不會有資格獲得GC這是我們想要的東西在你的情況。所以唯一預期的方式是撥打ThreadLocalMap#remove()
  2. 它創建很難找到的錯誤,因爲大多數時候使用您的ThreadLocal的線程是thread pool的一部分,因此線程將被重用於其他請求,因此如果您的ThreadLocal未被正確清理,線程將被重用您的對象實例存儲在您的ThreadLocal中,這可能與新請求無關,從而導致複雜的錯誤。所以在這裏例如我們可以得到不同用戶的結果,因爲ThreadLocal還沒有被清理。

因此模式如下:

try { 
    userInfo.set(new UserInfo(userId, name)); 
    // Some code here 
} finally { 
    // Clean up your thread local whatever happens 
    userInfo.remove(); 
} 

關於線程安全,這當然是線程安全的,即使UserInfo不是線程安全的,因爲每個線程都使用的UserInfo所以沒有實例它自己的實例的存儲到ThreadLocalUserInfo將被訪問或多個線程修改,因爲一個ThreadLocal值以某種方式作用域的當前線程。

+0

感謝您的全面回覆。 2跟進問題。 1)'ThreadLocal'存儲的對象怎麼不能被GC清理? 2)你是否說過,在我的例子中,如果'TheadLocal.remove()'從未被調用,代碼變得不是線程安全的? (即在'DOB()'我可能會得到另一個USERINFO) – Glide

+1

我提高了我的答案#1,#2我只能說,如果你不叫TheadLocal.remove(),你將面臨上述問題,TheadLocal類是線程安全的,並且即使不調用remove()也將保持線程安全。 –