摘要:那線程局部變量就是每個線程都會有一個局部變量,獨立于變量的初始化副本,而各個副本是通過線程唯一標識相關聯的。移除此線程局部變量當前線程的值。如果此線程局部變量隨后被當前線程讀取,且這期間當前線程沒有設置其值,則將調用其方法重新初始化其值。
前言
ThreadLocal網上資料很多,那我為什么還要寫下這篇文章呢?主要是想匯聚多篇文章的優秀之處以及我對于ThreadLocal的理解來加深印象,也使讀者能更全面的理解ThreadLocal的用法、原理和用途。
一、何謂“ThreadLocal”二、ThreadLocal的用法ThreadLocal是一個線程局部變量,我們都知道全局變量和局部變量的區別,拿Java舉例就是定義在類中的是全局的變量,各個方法中都能訪問得到,而局部變量定義在方法中,只能在方法內訪問。那線程局部變量(ThreadLocal)就是每個線程都會有一個局部變量,獨立于變量的初始化副本,而各個副本是通過線程唯一標識相關聯的。
(1)方法摘要
作用域 | 類型 | 方法 | 描述 |
---|---|---|---|
public | T | get() | 返回此線程局部變量的當前線程副本中的值 |
protected | T | initialValue() | 返回此線程局部變量的當前線程的“初始值” |
public | void | remove() | 移除此線程局部變量當前線程的值 |
public | void | set(T value) | 將此線程局部變量的當前線程副本中的值設置為指定值 |
注意事項:
==initialValue()== 這個方法是為了讓子類覆蓋設計的,默認缺省null。如果get()后又remove()則可能會在調用一下此方法。
==remove()== 移除此線程局部變量當前線程的值。如果此線程局部變量隨后被當前線程 讀取,且這期間當前線程沒有 設置其值,則將調用其 initialValue() 方法重新初始化其值。這將導致在當前線程多次調用 initialValue 方法。
(2)常規用法
在開始之前貼出一個公共的線程測試類
public class TaskThreadextends Thread{ private T t; public TaskThread(String threadName,T t) { this.setName(threadName); this.t = t; } @Override public void run() { for (int i = 0; i < 2; i++) { try { Class[] argsClass = new Class[0]; Method method = t.getClass().getMethod("getUniqueId",argsClass); int value = (int) method.invoke(t); System.out.println("thread[" + Thread.currentThread().getName() + "] --> uniqueId["+value+ "]"); } catch (NoSuchMethodException e) { // TODO 暫不處理 continue; } catch (IllegalAccessException e) { // TODO 暫不處理 continue; } catch (InvocationTargetException e) { // TODO 暫不處理 continue; } } } }
例1:為每個線程生成一個唯一的局部標識
public class UniqueThreadIdGenerator { // 原子整型 private static final AtomicInteger uniqueId = new AtomicInteger(0); // 線程局部整型變量 private static final ThreadLocaluniqueNum = new ThreadLocal < Integer > () { @Override protected Integer initialValue() { return uniqueId.getAndIncrement(); } }; //變量值 public static int getUniqueId() { return uniqueId.get(); } public static void main(String[] args) { UniqueThreadIdGenerator uniqueThreadId = new UniqueThreadIdGenerator(); // 為每個線程生成一個唯一的局部標識 TaskThread t1 = new TaskThread ("custom-thread-1", uniqueThreadId); TaskThread t2 = new TaskThread ("custom-thread-2", uniqueThreadId); TaskThread t3 = new TaskThread ("custom-thread-3", uniqueThreadId); t1.start(); t2.start(); t3.start(); } }
運行結果:
//每個線程的局部變量都是唯一的 thread[custom-thread-2] --> uniqueId[0] thread[custom-thread-2] --> uniqueId[0] thread[custom-thread-1] --> uniqueId[0] thread[custom-thread-1] --> uniqueId[0] thread[custom-thread-3] --> uniqueId[0] thread[custom-thread-3] --> uniqueId[0]
例2:為每個線程創建一個局部唯一的序列
public class UniqueSequenceGenerator { // 線程局部整型變量 private static final ThreadLocaluniqueNum = new ThreadLocal < Integer > () { @Override protected Integer initialValue() { return 0; } }; //變量值 public static int getUniqueId() { uniqueNum.set(uniqueNum.get() + 1); return uniqueNum.get(); } public static void main(String[] args) { UniqueSequenceGenerator uniqueThreadId = new UniqueSequenceGenerator(); // 為每個線程生成內部唯一的序列號 TaskThread t1 = new TaskThread ("custom-thread-1", uniqueThreadId); TaskThread t2 = new TaskThread ("custom-thread-2", uniqueThreadId); TaskThread t3 = new TaskThread ("custom-thread-3", uniqueThreadId); t1.start(); t2.start(); t3.start(); } }
運行結果:
thread[custom-thread-2] --> uniqueId[1] thread[custom-thread-2] --> uniqueId[2] thread[custom-thread-1] --> uniqueId[1] thread[custom-thread-1] --> uniqueId[2] thread[custom-thread-3] --> uniqueId[1] thread[custom-thread-3] --> uniqueId[2]三、ThreadLocal的原理(摘自網上)
(1)源碼解析
源碼實現片段:set
/** * Sets the current thread"s copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread"s copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
在這個方法內部我們看到,首先通過getMap(Thread t)方法獲取一個和當前線程相關的ThreadLocalMap,然后將變量的值設置到這個ThreadLocalMap對象中,當然如果獲取到的ThreadLocalMap對象為空,就通過createMap方法創建。
==線程隔離的秘密,就在于ThreadLocalMap這個類。ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲取(對比Map對象來理解),每個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當前線程讀取和修改。ThreadLocal類通過操作每一個線程特有的ThreadLocalMap副本,從而實現了變量訪問在不同線程中的隔離。因為每個線程的變量都是自己特有的,完全不會有并發錯誤。還有一點就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設置的對象了。== 這個就是實現原理
源碼實現片段:getMap、createMap
/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map * @param map the map to store. */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
源碼實現片段:get
/** * Returns the value in the current thread"s copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread"s value of this thread-local */ public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
源碼實現片段:setInitialValue
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } //獲取和當前線程綁定的值時,ThreadLocalMap對象是以this指向的ThreadLocal對象為鍵 //進行查找的,這當然和前面set()方法的代碼是相呼應的。進一步地,我們可以創建不同的 //ThreadLocal實例來實現多個變量在不同線程間的訪問隔離,為什么可以這么做?因為不 //同的ThreadLocal對象作為不同鍵,當然也可以在線程的ThreadLocalMap對象中設置不同 //的值了。通過ThreadLocal對象,在多線程中共享一個值和多個值的區別,就像你在一個 //HashMap對象中存儲一個鍵值對和多個鍵值對一樣,僅此而已。四、ThreadLocal實際用途
例1:在數據庫管理中的連接管理類是下面這樣的:(摘自網上)
public class ConnectionManager { private static Connection connect = null; public static Connection getConnection() { if(connect == null){ connect = DriverManager.getConnection(); } return connect; } ... }
在單線程的情況下這樣寫并沒有問題,但如果在多線程情況下回出現線程安全的問題。你可能會說用同步關鍵字或鎖來保障線程安全,這樣做當然是可行的,但考慮到性能的問題所以這樣子做并是很優雅。
下面是改造后的代碼:
public class ConnectionManager { private static ThreadLocalconnThreadLocal = new ThreadLocal (); public static Connection getConnection() { if(connThreadLocal.get() != null) return connThreadLocal.get(); //獲取一個連接并設置到當前線程變量中 Connection conn = getConnection(); connThreadLocal.set(conn); return conn; } ... }
例2:日期格式(摘自網上)
使用這個日期格式類主要作用就是將枚舉對象轉成Map而map的值則是使用ThreadLocal存儲,那么在實際的開發中可以在同一線程中的不同方法中使用日期格式而無需在創建日期格式的實例。
public class DateFormatFactory { public enum DatePattern { TimePattern("yyyy-MM-dd HH:mm:ss"), DatePattern("yyyy-MM-dd"); public String pattern; private DatePattern(String pattern) { this.pattern = pattern; } } private static final Map五、總結> pattern2ThreadLocal; static { DatePattern[] patterns = DatePattern.values(); int len = patterns.length; pattern2ThreadLocal = new HashMap >(len); for (int i = 0; i < len; i++) { DatePattern datePattern = patterns[i]; final String pattern = datePattern.pattern; pattern2ThreadLocal.put(datePattern, new ThreadLocal () { @Override protected DateFormat initialValue() { return new SimpleDateFormat(pattern); } }); } } //獲取DateFormat public static DateFormat getDateFormat(DatePattern pattern) { ThreadLocal threadDateFormat = pattern2ThreadLocal.get(pattern); //不需要判斷threadDateFormat是否為空 return threadDateFormat.get(); } public static void main(String[] args) { String dateStr = DateFormatFactory.getDateFormat(DatePattern.TimePattern).format(new Date()); System.out.println(dateStr); } }
ThreadLocal是用冗余的方式換時間,而鎖機制則是時間換空間,好的設計往往都是在時間、空間以及復雜度之間做權衡,道理是這樣但是真正能平衡三者之間的人我姑且稱之為“大成者”,愿你我在成長的道路上越走越遠。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70472.html
摘要:面試,是跳槽后第一個需要面對的問題而且不同公司面試的著重點不同但是卻有一個共同點基礎是必考的。對自動災難恢復有要求的表。 貌似這一點適應的行業最廣,但是我可以很肯定的說:當你從事Java一年后,重新找工作時,才會真實的感受到這句話。 工作第一年,往往是什么都充滿新鮮感,什么都學習,沖勁十足的一年;WEB行業知識更新特別快,今天一個框架的新版本,明天又是另一個新框架,有時往往根據項目的需...
摘要:面試,是跳槽后第一個需要面對的問題而且不同公司面試的著重點不同但是卻有一個共同點基礎是必考的。對自動災難恢復有要求的表。 貌似這一點適應的行業最廣,但是我可以很肯定的說:當你從事Java一年后,重新找工作時,才會真實的感受到這句話。 工作第一年,往往是什么都充滿新鮮感,什么都學習,沖勁十足的一年;WEB行業知識更新特別快,今天一個框架的新版本,明天又是另一個新框架,有時往往根據項目的需...
閱讀 3318·2023-04-25 16:25
閱讀 3823·2021-11-15 18:01
閱讀 1600·2021-09-10 11:21
閱讀 3007·2021-08-02 16:53
閱讀 3081·2019-08-30 15:55
閱讀 2489·2019-08-29 16:24
閱讀 2098·2019-08-29 13:14
閱讀 1027·2019-08-29 13:00