Java中的线程本地存储
開發人員中鮮為人知的功能之一是線程本地存儲。 這個想法很簡單,并且在需要數據的情況下很有用。 如果我們有兩個線程,則它們引用相同的全局變量,但我們希望它們具有彼此獨立初始化的單獨值。
大多數主要的編程語言都有該概念的實現。 例如,C ++ 11甚至具有thread_local關鍵字,Ruby選擇了一種API 方法 。
從1.2版開始,Java還使用java.lang.ThreadLocal <T>及其子類java.lang.InheritableThreadLocal <T>來實現該概念,因此這里沒有什么新鮮的東西。
假設由于某種原因,我們需要為線程指定一個Long特定值。 使用線程本地將很簡單:
public class ThreadLocalExample {public static class SomethingToRun implements Runnable {private ThreadLocal threadLocal = new ThreadLocal();@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());try {Thread.sleep(2000);} catch (InterruptedException e) {}threadLocal.set(System.nanoTime());System.out.println(Thread.currentThread().getName() + " " + threadLocal.get());}}public static void main(String[] args) {SomethingToRun sharedRunnableInstance = new SomethingToRun();Thread thread1 = new Thread(sharedRunnableInstance);Thread thread2 = new Thread(sharedRunnableInstance);thread1.start();thread2.start();}}以下代碼的一個可能示例運行將導致:
Thread-0 nullThread-0 132466384576241Thread-1 nullThread-1 132466394296347在開始時,兩個線程的值都設置為null,顯然每個線程都使用單獨的值,因為在將Thread-0的值設置為System.nanoTime()之后 ,它不會對Thread-1的值產生任何影響如我們所愿,一個線程作用域的long變量。
一個不錯的副作用是線程從各種類調用多個方法的情況。 他們都將能夠使用相同的線程范圍變量,而無需進行重大的API更改。 由于未明確傳遞價值,因此可能會難以測試且不利于設計,但這完全是一個單獨的主題。
在哪些地區流行使用線程局部語言的框架?
作為Java中最受歡迎的框架之一,Spring在內部將ThreadLocals用于許多部分,可通過簡單的github 搜索輕松顯示。 大多數用法與當前用戶的操作或信息有關。 實際上,這實際上是JavaEE世界中ThreadLocals的主要用途之一,例如在RequestContextHolder中存儲當前請求的信息:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal<RequestAttributes>("Request attributes");或UserCredentialsDataSourceAdapter中的當前JDBC連接用戶憑據。
如果我們回到RequestContextHolder,則可以使用此類訪問代碼中任何位置的所有當前請求信息。
常見的用例是LocaleContextHolder ,它可以幫助我們存儲當前用戶的語言環境。
Mockito使用它來存儲當前的“全局” 配置 ,如果我們看看那里的任何框架,那么我們也很有可能會找到它。
線程本機和內存泄漏
我們了解了這個很棒的小功能,所以讓我們在各處使用它。 我們可以做到這一點,但很少有Google搜索,而且我們發現那里的大多數人都說ThreadLocal是邪惡的。 并非完全正確,它是一個很好的實用程序,但在某些情況下可能容易造成內存泄漏。
“您是否可以通過線程局部變量導致意外的對象保留? 你當然可以。 但是您也可以使用數組來執行此操作。 這并不意味著線程局部變量(或數組)是壞事。 只是您必須謹慎使用它們。 使用線程池需要格外小心。 隨意使用線程池與隨意使用線程本地變量相結合會導致意外的對象保留,這在許多地方都已提到。 但是,將責任歸咎于線程本機是沒有根據的。” –約書亞·布洛赫(Joshua Bloch)
如果它在應用程序服務器上運行,則使用ThreadLocal在服務器代碼中創建內存泄漏非常容易。 ThreadLocal上下文與運行它的線程相關聯,一旦線程死掉,它將被丟棄。 現代的應用服務器使用線程池,而不是在每個請求上創建新線程,這意味著您最終可能會無限期地在應用程序中保存大型對象。 由于線程池來自應用程序服務器,因此即使卸載應用程序后,我們的內存泄漏仍會保留。 修復方法很簡單,可以釋放不需要的資源。
另一個ThreadLocal濫用是API設計。 我經??吹降教幎荚谑褂肦equestContextHolder (它持有ThreadLocal ),例如DAO層。 后來,如果有人在諸如和調度程序之類的請求之外調用相同的DAO方法,他將得到一個非常糟糕的驚喜。
這使黑魔法和許多維護開發人員最終找到了您的住所并進行了訪問。 即使ThreadLocal中的變量是線程本地的,它們在代碼中還是非常全局的。 使用它之前,請確保您確實需要此線程作用域。
有關該主題的更多信息
- http://en.wikipedia.org/wiki/Thread-local_storage
- http://www.appneta.com/blog/introduction-to-javas-threadlocal-storage/
- https://plumbr.eu/blog/how-to-shoot-yourself-in-foot-with-threadlocals
- http://stackoverflow.com/questions/817856/when-and-how-should-i-use-a-threadlocal-variable
- https://plumbr.eu/blog/when-and-how-to-use-a-threadlocal
- https://weblogs.java.net/blog/jjviana/archive/2010/06/09/dealing-glassfish-301-memory-leak-or-threadlocal-thread-pool-bad-ide
- https://software.intel.com/zh-CN/articles/use-thread-local-storage-to-reduce-synchronization
翻譯自: https://www.javacodegeeks.com/2014/12/thread-local-storage-in-java.html
總結
以上是生活随笔為你收集整理的Java中的线程本地存储的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 安卓的版权是谁的(安卓的版权)
- 下一篇: Java飞行记录器(JFR)