何时以及如何使用ThreadLocal
正如我們的讀者可能已經猜到的那樣,我每天都在處理內存泄漏。 最近,一種特殊類型的OutOfMemoryError消息開始引起我的注意-濫用ThreadLocals觸發的問題變得越來越頻繁。 在查看此類泄漏的原因時,我開始相信其中一半以上是由于開發人員導致的,他們要么不知道自己在做什么,要么正試圖對并非旨在解決的問題采用解決方案。 。
我決定不發表文章,而是發表兩篇文章來打開這個話題,您目前正在閱讀第一篇。 在帖子中,我解釋了ThreadLocal使用背后的動機。 在當前正在進行的第二篇文章中,我將打開ThreadLocal帽子并查看實現。
讓我們從一個虛構的場景開始,其中ThreadLocal的使用確實是合理的。 為此,請與我們的假設開發人員Tim打個招呼。 蒂姆正在開發一個Web應用程序,其中包含許多本地化內容。 例如,來自加利福尼亞的用戶可能希望使用熟悉的MM / dd / yy模式格式化日期,而來自愛沙尼亞的用戶則希望看到根據dd.MM.yyyy格式化的日期。 因此,Tim開始編寫如下代碼:
public String formatCurrentDate() {DateFormat df = new SimpleDateFormat("MM/dd/yy");return df.format(new Date());}public String formatFirstOfJanyary1970() {DateFormat df = new SimpleDateFormat("MM/dd/yy");return df.format(new Date(0));}過了一會兒,Tim覺得這很無聊并且違反了良好做法–應用程序代碼被此類初始化污染了。 因此,他通過將DateFormat提取到實例變量中采取了看似合理的措施。 采取行動之后,他的代碼現在如下所示:
private DateFormat df = new SimpleDateFormat("MM/dd/yy");public String formatCurrentDate() {return df.format(new Date());}public String formatFirstOfJanyary1970() {return df.format(new Date(0));}Tim對重構結果感到滿意,Tim向自己扔了一個假想的高五,將更改推送到存儲庫中,然后回家。 幾天后,用戶開始抱怨–其中一些字符串似乎完全亂碼,而不是以前格式正確的日期。
研究問題Tim發現DateFormat實現不是線程安全的。 這意味著在上述情況下,如果兩個線程同時使用formatCurrentDate()和formatFirstOfJanyary1970()方法,則狀態可能會混亂,并且顯示的結果可能會混亂。 因此,Tim通過限制對方法的訪問以確保一次輸入一個線程進入格式化功能來解決此問題。 現在,他的代碼如下所示:
private DateFormat df = new SimpleDateFormat("MM/dd/yy");public synchronized String formatCurrentDate() {return df.format(new Date());}public synchronized String formatFirstOfJanyary1970() {return df.format(new Date(0));}在給自己另一個虛擬的高五后,蒂姆做出了改變并去了一個漫長的假期。 第二天才開始接收電話,抱怨應用程序的吞吐量急劇下降。 深入研究問題后,他發現同步訪問已在應用程序中造成了意外的瓶頸。 現在,線程不必再隨意輸入格式化部分了,而必須彼此等待。
進一步閱讀該問題,Tim發現了另一種類型的變量ThreadLocal 。 這些變量與普通變量不同,因為每個訪問一個線程(通過ThreadLocal的get或set方法)的線程都有其自己的,獨立初始化的變量副本。 對于新發現的概念感到滿意,Tim再次重寫了代碼:
public static ThreadLocal df = new ThreadLocal() {protected DateFormat initialValue() {return new SimpleDateFormat("MM/dd/yy");}};public String formatCurrentDate() {return df.get().format(new Date());}public String formatFirstOfJanyary1970() {return df.get().format(new Date(0));}經過這樣的過程,蒂姆通過痛苦的教訓學到了一個強大的概念。 像在最后一個示例中那樣應用后,結果可以很好地說明收益。
但是新發現的概念很危險。 如果Tim使用了應用程序類之一而不是引導類加載器加載的JDK捆綁的DateFormat類,那么我們已經處在危險區域。 只是忘記在手頭的任務完成后將其刪除,該對象的副本將保留在線程中,該線程通常屬于線程池。 由于池化線程的壽命超過了應用程序的壽命,因此它將防止該對象,從而使ClassLoader負責加載應用程序而不會被垃圾回收。 而且我們創建了一個泄漏,有機會以一種很好的舊java.lang.OutOfMemoryError:PermGen空間形式浮出水面。
另一種開始濫用該概念的方法是通過使用ThreadLocal作為在應用程序中獲取全局上下文的黑客。 克服這個難題是確保應用程序代碼具有各種無法想象的依賴關系的可靠方式,將整個代碼庫耦合到一個無法維護的混亂中。
翻譯自: https://www.javacodegeeks.com/2013/10/when-and-how-to-use-a-threadlocal.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的何时以及如何使用ThreadLocal的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑硬盘废物利用(废旧的电脑硬盘还可以这
- 下一篇: 三国演义魏国代表人物介绍(魏国主要名将历