java - 當類暴露給線程池時,清理ThreadLocal資源真的是我的工作嗎?




concurrency threadpool thread-local (5)

我認為JDK的ThreadPoolExecutor可以在任務執行後進行ThreadLocals清理,但我們知道它沒有。 我認為它至少可以提供一個選項。 原因可能是因為Thread只提供對其TreadLocal映射的包私有訪問,因此ThreadPoolExecutor無法在不更改Thread的API的情況下訪問它們。

有趣的是,ThreadPoolExecutor These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals... beforeExecutionafterExecution都有受保護的方法存根,API說: These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals... These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals... 所以我可以設想一個實現ThreadLocalCleaner接口的Task和我們的自定義ThreadPoolExecutor,它在afterExecution上調用task的cleanThreadLocals();

我使用ThreadLocal

在我的Java類中,我有時主要使用ThreadLocal作為避免不必要的對象創建的方法:

@net.jcip.annotations.ThreadSafe
public class DateSensitiveThing {

    private final Date then;

    public DateSensitiveThing(Date then) {
        this.then = then;
    }

    private static final ThreadLocal<Calendar> threadCal = new ThreadLocal<Calendar>()   {
        @Override
        protected Calendar initialValue() {
            return new GregorianCalendar();
        }
    };

    public Date doCalc(int n) {
        Calendar c = threadCal.get();
        c.setTime(this.then):
        // use n to mutate c
        return c.getTime();
    }
}

我這樣做是出於正當的原因GregorianCalendar是那些光榮的有狀態,可變,非線程安全的對象之一,它提供跨多個調用的服務,而不是代表一個值。 此外,它被認為是“昂貴的”實例化(這是否真實不是這個問題的重點)。 (總的來說,我真的很佩服它:-))

Tomcat如何發牢騷

但是,如果我在任何聚集線程的環境中使用這樣的類 - 並且我的應用程序無法控制這些線程的生命週期 - 那麼就有可能發生內存洩漏。 Servlet環境就是一個很好的例子。

事實上,當一個webapp停止時,Tomcat 7就像這樣嘶嘶作響:

嚴重:Web應用程序[]創建了一個ThreadLocal,其鍵為[org.apache.xmlbeans.impl.store.CharUtil $ 1](值[[email protected]])和一個值類型為[java.lang.ref.SoftReference](值[[email protected]]),但在Web應用程序停止時無法將其刪除。 線程將隨著時間的推移而更新,以避免可能的內存洩漏。 2012年12月13日下午12:54:30 org.apache.catalina.loader.WebappClassLoader checkThreadLocalMapForLeaks

(在特定情況下,甚至我的代碼都沒有這樣做)。

誰應該受到責備?

這似乎不太公平。 Tomcat責備 (或我班級的用戶)做正確的事。

最終,這是因為Tomcat希望重用它為我提供的線程,以及其他 Web應用程序。 (呃 - 我覺得很髒。)可能,這對Tomcat而言並不是一個很好的策略 - 因為線程確實有/導致狀態 - 不要在應用程序之間共享它們。

但是,這項政策至少是常見的,即使這是不可取的。 我覺得我有義務 - 作為ThreadLocal用戶,為我的類提供一種方法來“釋放”我的類附加到各種線程的資源。

但該怎麼辦呢?

這裡做什麼是正確的?

對我來說,似乎servlet引擎的線程重用策略與ThreadLocal背後的意圖不一致。

但也許我應該提供一個工具來允許用戶說“與這個類關聯的惡意,特定於線程的特定狀態,即使我無法讓線程死掉並讓GC做它的事情?”。 我甚至可以這樣做嗎? 我的意思是,我不能安排ThreadLocal#initialValue()在過去某個時間看過ThreadLocal#initialValue()每個Thread上調用。 或者還有另一種方式嗎?

或者我應該對我的用戶說“去為自己做一個體面的類加載器和線程池實現”?

編輯#1 :澄清瞭如何在不知道線程生命週期的vanailla實用程序類中使用threadCal 編輯#2 :修復了DateSensitiveThing的線程安全問題


在考慮了這一年之後,我認為JavaEE容器在不相關的應用程序的實例之間共享池化的工作線程是不可接受的。 這根本不是“企業”。

如果您真的要共享線程, java.lang.Thread (至少在JavaEE環境中)應該支持setContextState(int key)forgetContextState(int key) (鏡像setClasLoaderContext() )等方法,這些方法允許容器隔離特定於應用程序的ThreadLocal狀態,因為它在各種應用程序之間交換線程。

java.lang命名空間中進行此類修改之後,應用程序部署者只能採用“一個線程池,相關應用程序的一個實例”規則,並且應用程序開發人員認為'這個線程是我的,直到ThreadDeath我們做部分'。


由於線程不是由你創建的,它只是由你租用的,我認為在停止使用之前需要清理它是公平的 - 就像你在返回時填滿租來的汽車的坦克一樣。 Tomcat可以自己清理所有東西,但是它幫了你一個忙,提醒你忘記的東西。

ADD:你使用準備好的GregorianCalendar的方式是完全錯誤的:因為服務請求可以是並發的,並且沒有同步, doCalc可以採用另一個請求調用的getTime ater setTime 。 引入同步會使事情變慢,因此創建新的GregorianCalendar可能是更好的選擇。

換句話說,您的問題應該是:如何保留準備好的GregorianCalendar實例池,以便將其數量調整為請求率。 因此,至少需要一個包含該池的單例。 每個Ioc容器都有管理單例的方法,而且大多數都有現成的對像池實現。 如果您還沒有使用IoC容器,請開始使用一個(String,Guice),而不是重新發明輪子。


如果有任何幫助,我使用自定義SPI(接口)和JDK ServiceLoader 。 然後,我需要卸載threadlocals的所有內部庫(jar)都遵循ServiceLoader模式。 因此,如果jar需要threadlocal清理,如果它具有相應的/META-INF/services/interface.name ,它將自動被選中。

然後我在過濾器或監聽器中卸載(我有一些聽眾的問題,但我不記得是什麼)。

如果JDK / JEE帶有用於清除threadlocals的標準 SPI,那將是理想的選擇。


最簡單的解決方案是創建兩個線性佈局,一個使用按鈕,另一個使用列表視圖(在按鈕高度上包裝內容並在列表佈局高度上匹配父級)。 那麼只能用列表視圖在佈局上滾動視圖,而按鈕佈局將被忽略。 希望它有幫助,對不起,我不想寫出代碼。





java concurrency threadpool thread-local