摘要:那線程局部變量就是每個(gè)線程都會有一個(gè)局部變量,獨(dú)立于變量的初始化副本,而各個(gè)副本是通過線程唯一標(biāo)識相關(guān)聯(lián)的。移除此線程局部變量當(dāng)前線程的值。如果此線程局部變量隨后被當(dāng)前線程讀取,且這期間當(dāng)前線程沒有設(shè)置其值,則將調(diào)用其方法重新初始化其值。
前言
ThreadLocal網(wǎng)上資料很多,那我為什么還要寫下這篇文章呢?主要是想?yún)R聚多篇文章的優(yōu)秀之處以及我對于ThreadLocal的理解來加深印象,也使讀者能更全面的理解ThreadLocal的用法、原理和用途。
一、何謂“ThreadLocal”二、ThreadLocal的用法ThreadLocal是一個(gè)線程局部變量,我們都知道全局變量和局部變量的區(qū)別,拿Java舉例就是定義在類中的是全局的變量,各個(gè)方法中都能訪問得到,而局部變量定義在方法中,只能在方法內(nèi)訪問。那線程局部變量(ThreadLocal)就是每個(gè)線程都會有一個(gè)局部變量,獨(dú)立于變量的初始化副本,而各個(gè)副本是通過線程唯一標(biāo)識相關(guān)聯(lián)的。
(1)方法摘要
作用域 | 類型 | 方法 | 描述 |
---|---|---|---|
public | T | get() | 返回此線程局部變量的當(dāng)前線程副本中的值 |
protected | T | initialValue() | 返回此線程局部變量的當(dāng)前線程的“初始值” |
public | void | remove() | 移除此線程局部變量當(dāng)前線程的值 |
public | void | set(T value) | 將此線程局部變量的當(dāng)前線程副本中的值設(shè)置為指定值 |
注意事項(xiàng):
==initialValue()== 這個(gè)方法是為了讓子類覆蓋設(shè)計(jì)的,默認(rèn)缺省null。如果get()后又remove()則可能會在調(diào)用一下此方法。
==remove()== 移除此線程局部變量當(dāng)前線程的值。如果此線程局部變量隨后被當(dāng)前線程 讀取,且這期間當(dāng)前線程沒有 設(shè)置其值,則將調(diào)用其 initialValue() 方法重新初始化其值。這將導(dǎo)致在當(dāng)前線程多次調(diào)用 initialValue 方法。
(2)常規(guī)用法
在開始之前貼出一個(gè)公共的線程測試類
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:為每個(gè)線程生成一個(gè)唯一的局部標(biāo)識
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(); // 為每個(gè)線程生成一個(gè)唯一的局部標(biāo)識 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(); } }
運(yùn)行結(jié)果:
//每個(gè)線程的局部變量都是唯一的 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:為每個(gè)線程創(chuàng)建一個(gè)局部唯一的序列
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(); // 為每個(gè)線程生成內(nèi)部唯一的序列號 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(); } }
運(yùn)行結(jié)果:
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的原理(摘自網(wǎng)上)
(1)源碼解析
源碼實(shí)現(xiàn)片段: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); }
在這個(gè)方法內(nèi)部我們看到,首先通過getMap(Thread t)方法獲取一個(gè)和當(dāng)前線程相關(guān)的ThreadLocalMap,然后將變量的值設(shè)置到這個(gè)ThreadLocalMap對象中,當(dāng)然如果獲取到的ThreadLocalMap對象為空,就通過createMap方法創(chuàng)建。
==線程隔離的秘密,就在于ThreadLocalMap這個(gè)類。ThreadLocalMap是ThreadLocal類的一個(gè)靜態(tài)內(nèi)部類,它實(shí)現(xiàn)了鍵值對的設(shè)置和獲?。▽Ρ萂ap對象來理解),每個(gè)線程中都有一個(gè)獨(dú)立的ThreadLocalMap副本,它所存儲的值,只能被當(dāng)前線程讀取和修改。ThreadLocal類通過操作每一個(gè)線程特有的ThreadLocalMap副本,從而實(shí)現(xiàn)了變量訪問在不同線程中的隔離。因?yàn)槊總€(gè)線程的變量都是自己特有的,完全不會有并發(fā)錯(cuò)誤。還有一點(diǎn)就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設(shè)置的對象了。== 這個(gè)就是實(shí)現(xiàn)原理
源碼實(shí)現(xiàn)片段: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); }
源碼實(shí)現(xiàn)片段: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(); }
源碼實(shí)現(xiàn)片段: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; } //獲取和當(dāng)前線程綁定的值時(shí),ThreadLocalMap對象是以this指向的ThreadLocal對象為鍵 //進(jìn)行查找的,這當(dāng)然和前面set()方法的代碼是相呼應(yīng)的。進(jìn)一步地,我們可以創(chuàng)建不同的 //ThreadLocal實(shí)例來實(shí)現(xiàn)多個(gè)變量在不同線程間的訪問隔離,為什么可以這么做?因?yàn)椴? //同的ThreadLocal對象作為不同鍵,當(dāng)然也可以在線程的ThreadLocalMap對象中設(shè)置不同 //的值了。通過ThreadLocal對象,在多線程中共享一個(gè)值和多個(gè)值的區(qū)別,就像你在一個(gè) //HashMap對象中存儲一個(gè)鍵值對和多個(gè)鍵值對一樣,僅此而已。四、ThreadLocal實(shí)際用途
例1:在數(shù)據(jù)庫管理中的連接管理類是下面這樣的:(摘自網(wǎng)上)
public class ConnectionManager { private static Connection connect = null; public static Connection getConnection() { if(connect == null){ connect = DriverManager.getConnection(); } return connect; } ... }
在單線程的情況下這樣寫并沒有問題,但如果在多線程情況下回出現(xiàn)線程安全的問題。你可能會說用同步關(guān)鍵字或鎖來保障線程安全,這樣做當(dāng)然是可行的,但考慮到性能的問題所以這樣子做并是很優(yōu)雅。
下面是改造后的代碼:
public class ConnectionManager { private static ThreadLocalconnThreadLocal = new ThreadLocal (); public static Connection getConnection() { if(connThreadLocal.get() != null) return connThreadLocal.get(); //獲取一個(gè)連接并設(shè)置到當(dāng)前線程變量中 Connection conn = getConnection(); connThreadLocal.set(conn); return conn; } ... }
例2:日期格式(摘自網(wǎng)上)
使用這個(gè)日期格式類主要作用就是將枚舉對象轉(zhuǎn)成Map而map的值則是使用ThreadLocal存儲,那么在實(shí)際的開發(fā)中可以在同一線程中的不同方法中使用日期格式而無需在創(chuàng)建日期格式的實(shí)例。
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五、總結(jié)> 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是用冗余的方式換時(shí)間,而鎖機(jī)制則是時(shí)間換空間,好的設(shè)計(jì)往往都是在時(shí)間、空間以及復(fù)雜度之間做權(quán)衡,道理是這樣但是真正能平衡三者之間的人我姑且稱之為“大成者”,愿你我在成長的道路上越走越遠(yuǎn)。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/70472.html
摘要:中的詳解必修個(gè)多線程問題總結(jié)個(gè)多線程問題總結(jié)有哪些源代碼看了后讓你收獲很多,代碼思維和能力有較大的提升有哪些源代碼看了后讓你收獲很多,代碼思維和能力有較大的提升開源的運(yùn)行原理從虛擬機(jī)工作流程看運(yùn)行原理。 自己實(shí)現(xiàn)集合框架 (三): 單鏈表的實(shí)現(xiàn) 自己實(shí)現(xiàn)集合框架 (三): 單鏈表的實(shí)現(xiàn) 基于 POI 封裝 ExcelUtil 精簡的 Excel 導(dǎo)入導(dǎo)出 由于 poi 本身只是針對于 ...
摘要:面試,是跳槽后第一個(gè)需要面對的問題而且不同公司面試的著重點(diǎn)不同但是卻有一個(gè)共同點(diǎn)基礎(chǔ)是必考的。對自動(dòng)災(zāi)難恢復(fù)有要求的表。 貌似這一點(diǎn)適應(yīng)的行業(yè)最廣,但是我可以很肯定的說:當(dāng)你從事Java一年后,重新找工作時(shí),才會真實(shí)的感受到這句話。 工作第一年,往往是什么都充滿新鮮感,什么都學(xué)習(xí),沖勁十足的一年;WEB行業(yè)知識更新特別快,今天一個(gè)框架的新版本,明天又是另一個(gè)新框架,有時(shí)往往根據(jù)項(xiàng)目的需...
摘要:面試,是跳槽后第一個(gè)需要面對的問題而且不同公司面試的著重點(diǎn)不同但是卻有一個(gè)共同點(diǎn)基礎(chǔ)是必考的。對自動(dòng)災(zāi)難恢復(fù)有要求的表。 貌似這一點(diǎn)適應(yīng)的行業(yè)最廣,但是我可以很肯定的說:當(dāng)你從事Java一年后,重新找工作時(shí),才會真實(shí)的感受到這句話。 工作第一年,往往是什么都充滿新鮮感,什么都學(xué)習(xí),沖勁十足的一年;WEB行業(yè)知識更新特別快,今天一個(gè)框架的新版本,明天又是另一個(gè)新框架,有時(shí)往往根據(jù)項(xiàng)目的需...
閱讀 3333·2023-04-25 16:25
閱讀 3865·2021-11-15 18:01
閱讀 1622·2021-09-10 11:21
閱讀 3032·2021-08-02 16:53
閱讀 3095·2019-08-30 15:55
閱讀 2502·2019-08-29 16:24
閱讀 2113·2019-08-29 13:14
閱讀 1050·2019-08-29 13:00