Java中的ThreadLocal深入理解
提到ThreadLocal,有些Android或者Java程式設計師可能有所陌生,可能會提出種種問題,它是做什麼的,是不是和執行緒有關,怎麼使用呢?等等問題,本文將總結一下我對ThreadLocal的理解和認識,希望讓大家理解ThreadLocal更加透徹一些。
ThreadLocal是什麼
ThreadLocal是一個關於建立執行緒區域性變數的類。
通常情況下,我們建立的變數是可以被任何一個執行緒訪問並修改的。而使用ThreadLocal建立的變數只能被當前執行緒訪問,其他執行緒則無法訪問和修改。
Global && Local
上面的兩個修飾看似矛盾,實則不然。
- Global 意思是在當前執行緒中,任何一個點都可以訪問到ThreadLocal的值。
- Local 意思是該執行緒的ThreadLocal只能被該執行緒訪問,一般情況下其他執行緒訪問不到。
用法簡介
建立,支援泛型
ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>();
set方法
mStringThreadLocal.set("droidyue.com");
get方法
mStringThreadLocal.get();
完整的使用示例
private void testThreadLocal() { Thread t = new Thread() { ThreadLocal<String> mStringThreadLocal = new ThreadLocal<>(); @Override public void run() { super.run(); mStringThreadLocal.set("droidyue.com"); mStringThreadLocal.get(); } }; t.start(); }
ThreadLocal初始值
為ThreadLocal設定預設的get初始值,需要重寫initialValue方法,下面是一段程式碼,我們將預設值修改成了執行緒的名字
ThreadLocal<String> mThreadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return Thread.currentThread().getName(); } };
Android中的應用
在Android中,Looper類就是利用了ThreadLocal的特性,保證每個執行緒只存在一個Looper物件。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
如何實現
為了更好的掌握ThreadLocal,我認為了解其內部實現是很有必要的,這裡我們以set方法從起始看一看ThreadLocal的實現原理。
下面是ThreadLocal的set方法,大致意思為
- 首先獲取當前執行緒
- 利用當前執行緒作為控制程式碼獲取一個ThreadLocalMap的物件
- 如果上述ThreadLocalMap物件不為空,則設定值,否則建立這個ThreadLocalMap物件並設定值
原始碼如下
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
下面是一個利用Thread物件作為控制程式碼獲取ThreadLocalMap物件的程式碼
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
上面的程式碼獲取的實際上是Thread物件的threadLocals變數,可參考下面程式碼
class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; }
而如果一開始設定,即ThreadLocalMap物件未建立,則新建ThreadLocalMap物件,並設定初始值。
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
總結:實際上ThreadLocal的值是放入了當前執行緒的一個ThreadLocalMap例項中,所以只能在本執行緒中訪問,其他執行緒無法訪問。
物件存放在哪裡
在Java中,棧記憶體歸屬於單個執行緒,每個執行緒都會有一個棧記憶體,其儲存的變數只能在其所屬執行緒中可見,即棧記憶體可以理解成執行緒的私有記憶體。而堆記憶體中的物件對所有執行緒可見。堆記憶體中的物件可以被所有執行緒訪問。
問:那麼是不是說ThreadLocal的例項以及其值存放在棧上呢?
其實不是,因為ThreadLocal例項實際上也是被其建立的類持有(更頂端應該是被執行緒持有)。而ThreadLocal的值其實也是被執行緒例項持有。
它們都是位於堆上,只是通過一些技巧將可見性修改成了執行緒可見。
關於堆和棧的比較,請參考Java中的堆和棧的區別
真的只能被一個執行緒訪問麼
既然上面提到了ThreadLocal只對當前執行緒可見,是不是說ThreadLocal的值只能被一個執行緒訪問呢?
使用InheritableThreadLocal可以實現多個執行緒訪問ThreadLocal的值。
如下,我們在主執行緒中建立一個InheritableThreadLocal的例項,然後在子執行緒中得到這個InheritableThreadLocal例項設定的值。
private void testInheritableThreadLocal() { final ThreadLocal threadLocal = new InheritableThreadLocal(); threadLocal.set("droidyue.com"); Thread t = new Thread() { @Override public void run() { super.run(); Log.i(LOGTAG, "testInheritableThreadLocal =" + threadLocal.get()); } }; t.start(); }
上面的程式碼輸出的日誌資訊為
I/MainActivity( 5046): testInheritableThreadLocal =droidyue.com
使用InheritableThreadLocal可以將某個執行緒的ThreadLocal值在其子執行緒建立時傳遞過去。因為線上程建立過程中,有相關的處理邏輯。
//Thread.java private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { //code goes here if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
上面程式碼就是線上程建立的時候,複製父執行緒的inheritableThreadLocals的資料。
會導致記憶體洩露麼
有網上討論說ThreadLocal會導致記憶體洩露,原因如下
- 首先ThreadLocal例項被執行緒的ThreadLocalMap例項持有,也可以看成被執行緒持有。
- 如果應用使用了執行緒池,那麼之前的執行緒例項處理完之後出於複用的目的依然存活
- 所以,ThreadLocal設定的值被持有,導致記憶體洩露。
上面的邏輯是清晰的,可是ThreadLocal並不會產生記憶體洩露,因為ThreadLocalMap做選擇key的時候,並不是直接選擇ThreadLocal例項,而是ThreadLocalMap例項的弱引用。
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } }
所以實際上從ThreadLocal設計角度來說是不會導致記憶體洩露的。關於弱引用,瞭解更多,請訪問譯文:理解Java中的弱引用
使用場景
- 實現單個執行緒單例以及單個執行緒上下文資訊儲存,比如交易id等
- 實現執行緒安全,非執行緒安全的物件使用ThreadLocal之後就會變得執行緒安全,因為每個執行緒都會有一個對應的例項
- 承載一些執行緒相關的資料,避免在方法中來回傳遞引數
參考文章
- Java ThreadLocal
- Threadlocals and memory leaks in J2EE
- Java Thread Local – How to use and code sample
- ThreadLocal in Java – Example Program and Tutorial
相關文章
- 深入理解ThreadLocalthread
- 深入理解Java中的鎖Java
- 深入理解 Java 中的 LambdaJava
- 深入理解Java中的AQSJavaAQS
- 深入理解ThreadLocal及其變種thread
- 深入理解java:執行緒本地變數 java.lang.ThreadLocal類Java執行緒變數thread
- 深入理解Java中的逃逸分析Java
- 深入理解Java中的鎖(一)Java
- 深入理解Java中的鎖(二)Java
- 深入理解Java中的Garbage CollectionJava
- JAVA中鎖的深入理解與解析Java
- 深入理解Java中的不可變物件Java物件
- 深入理解 Java 中的 try-with-resourceJava
- Java中的引用與ThreadLocalJavathread
- Java中的ThreadLocal詳解Javathread
- 深入理解 Java 中 SPI 機制Java
- 深入理解Java中的HashMap的實現原理JavaHashMap
- 深入研究java.lang.ThreadLocal類Javathread
- 深入理解Java中的volatile關鍵字Java
- 深入理解Java中的final關鍵字Java
- 深入理解java中的組合和繼承Java繼承
- 深入理解Java中的記憶體洩漏Java記憶體
- Java中HashMap和TreeMap的區別深入理解JavaHashMap
- Java面試題:請談談對ThreadLocal的理解?Java面試題thread
- ThreadLocal理解thread
- 理解ThreadLocalthread
- Java中Thread 和Runnable 深入理解Javathread
- 深入理解 Java 中 protected 修飾符Java
- Java中ThreadLocal的用法和原理Javathread
- Java併發程式設計:深入剖析ThreadLocalJava程式設計thread
- 深入研究java.lang.ThreadLocal類薦Javathread
- 深入理解Java序列化中的SerialVersionUidJavaUI
- Java執行緒中斷的本質深入理解Java執行緒
- JS中this的深入理解JS
- 深入理解Js中的thisJS
- ThreadLocal的簡單理解thread
- ThreadLocal在java web工程中的使用。threadJavaWeb
- 深入理解Java的==和equalsJava