ThreadLocal使用以及原理
阿新 • • 發佈:2018-07-06
read oci purpose 應用 eth ati ria ini mem
介紹
- ThreadLocal是一個用於創建線程局部變量的類。當前線程通過ThreadLocal的set()方法設置的變量只對當前線程可見,通過get()獲取設置的變量。
使用
- 支持泛型
ThreadLocal<String> threadLocal = new ThreadLocal<>();
- 當前線程通過ThreadLocal對象的set(value)/get()設置變量和獲取設置的變量
threadLocal.set("jinshuai");
threadLocal.get();
原理
- 每個線程Thread維護一個ThreadLocalMap<key,value>
- key是ThreadLocal對象,value是通過set(value)設置的值
- key是ThreadLocal對象,value是通過set(value)設置的值
- 當前線程調用threadLocal.set("jinshuai");
- 首先獲取當前線程所維護的ThreadLocalMap
- 然後判斷當前線程是否已經創建過這個ThreadLocalMap
- 如果已經創建,會將ThreadLocal對象當作key,和當前線程要設置的值當作value放到ThreadLocalMap。
- 如果沒有創建,會創建並初始化一個ThreadLocalMap(類似HashMap 初始化數組長度為2的冪,設置擴充閾值...)然後同上↑
public void set(T value) { // 獲取當前線程對象 Thread t = Thread.currentThread(); // 獲取線程的ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } // 獲取ThreadLocalMap ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
- 當前線程調用threadLocal.get();
- 首先獲取當前線程所維護的ThereadLocalMap
- 然後將ThreadLocal對象作為key獲取對應的Entry
- 如果Entry不為空獲取Entry的value
- 如果Entry為空直接返回一個setInitvalue()值也就是null
public T get() { // 獲取當前線程對象 Thread t = Thread.currentThread(); // 獲取當前線程對應的ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { // 將ThreadLocal(this)作為key獲取entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") // 獲取entry的value T result = (T)e.value; return result; } } // 如果entry為空 return setInitialValue(); }
應用場景
- 如果一個對象非線程安全,但又不想通過加鎖的方式實現線程安全,可以通過ThreadLocal.set()對象的值,比如SimpleDataFormat不是線程安全的,此時可以每個線程設置一個SimpleDataFormat對象
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
public String formatIt(Date date)
{
return formatter.get().format(date);
}
- 當某一個變量比如User對象在多個方法中傳遞時,會變得比較亂此時可以通過ThreadLocal設置變量
比如在Servlet中:
doGet(HttpServletRequest req, HttpServletResponse resp) { User user = getLoggedInUser(req); doSomething(user) doSomethingElse(user) renderResponse(resp,user) }
每個方法都需要一個user,不夠優雅(咳咳...),此時可以通過設置一個ThreadLocal單例,然後set(user):
doGet(HttpServletRequest req, HttpServletResponse resp) { User user = getLoggedInUser(req); ThreadLocalSingleInstace.getThreadLocal().set(user) try { doSomething() doSomethingElse() renderResponse(resp) } finally { ThreadLocalSingleInstace.getThreadLocal().remove() } } // 獲取ThreadLocal單例 class ThreadLocalSingleInstace { static private ThreadLocal threadLocal = new ThreadLocal<User>(); static ThreadLocal<User> getThreadLocal() { return threadLocal; } }
註意
- 用完以後應該調用remove()移除設定的值,防止內存泄漏
// 會移除這個Entry
threadLocal.remove();
- 在線程池中由於線程會被復用,所以不會停止,導致每個線程的ThreadLocalMap裏的key是ThreadLocal的弱引用,GC時會將其回收,但是其對應的value一直有一個強引用不會被回收造成內存泄漏。
/**
* 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;
}
}
參考
- https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
- http://blog.xiaohansong.com/2016/08/06/ThreadLocal-memory-leak/#
- https://stackoverflow.com/questions/817856/when-and-how-should-i-use-a-threadlocal-variable
- https://stackoverflow.com/questions/1490919/purpose-of-threadlocal
ThreadLocal使用以及原理