這篇文章主要介紹“java中ThreadLocal有什么作用”,在日常操作中,相信很多人在java中ThreadLocal有什么作用問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”java中ThreadLocal有什么作用”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),故城企業(yè)網(wǎng)站建設(shè),故城品牌網(wǎng)站建設(shè),網(wǎng)站定制,故城網(wǎng)站建設(shè)報(bào)價(jià),網(wǎng)絡(luò)營(yíng)銷(xiāo),網(wǎng)絡(luò)優(yōu)化,故城網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強(qiáng)企業(yè)競(jìng)爭(zhēng)力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時(shí)我們時(shí)刻保持專(zhuān)業(yè)、時(shí)尚、前沿,時(shí)刻以成就客戶成長(zhǎng)自我,堅(jiān)持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實(shí)用型網(wǎng)站。
內(nèi)存模型中所有變量存儲(chǔ)在主內(nèi)存中,當(dāng)一個(gè)線程中要使用某個(gè)變量時(shí),需要從主內(nèi)存復(fù)制該變量到其線程內(nèi)才能操作,此時(shí)線程中操作的是主內(nèi)存變量的副本,操作完成后再刷回主內(nèi)存。刷回的實(shí)質(zhì)就是變量賦值
如果多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),每個(gè)線程都具有一個(gè)副本,操作完畢后都會(huì)刷回主內(nèi)存,刷回時(shí)間存在先后,則賦值有先后,當(dāng)然后者會(huì)覆蓋前者,這是造成可見(jiàn)性問(wèn)題的次要原因。
引入以上知識(shí)點(diǎn)后,再來(lái)說(shuō)明ThreadLocal。一個(gè)線程想使用某個(gè)變量,于是從主內(nèi)存復(fù)制該共享變量到線程內(nèi)部中。使用完畢后想再下次再次使用該變量時(shí),得到的變量副本是上次使用的副本,而不是從主內(nèi)存的變量再次復(fù)制過(guò)來(lái)的副本,并且不想讓其他線程影響到該變量。這就是ThreadLocal的目的,其實(shí)現(xiàn)不是通過(guò)共享變量這種方式實(shí)現(xiàn)的,詳細(xì)內(nèi)容下面介紹
目的很明確,但是身處JAVA內(nèi)存模型中要遵循內(nèi)存模型規(guī)范,下面看看JDK是如何即滿足內(nèi)存模型規(guī)范,又滿足ThreadLocal目的。
這點(diǎn)很簡(jiǎn)單,就是你該怎么樣還怎么樣,仍然受你管轄,該復(fù)制就復(fù)制,該刷回就刷回,不可見(jiàn)還是會(huì)造成不可見(jiàn)。
多個(gè)線程都能訪問(wèn)的變量才叫共享變量,如果控制變量的訪問(wèn)方式,使其他線程線程不能訪問(wèn)就可以了??刂品绞骄褪菍⒕€程與變量的一一對(duì)應(yīng),將該變量的訪問(wèn)入口控制到只有該線程即可,JDK中的做法就是讓線程持有這個(gè)變量(綁定到線程本身)
線程可以綁定變量,但是并不知道需要綁定多少個(gè),于是將這個(gè)存儲(chǔ)功能還是交給專(zhuān)門(mén)的數(shù)據(jù)結(jié)構(gòu)—>Map。并且還專(zhuān)門(mén)設(shè)計(jì)了一個(gè)用來(lái)訪問(wèn)這個(gè)Map的工具,這個(gè)工具就是ThreadLocal。并且這個(gè)Map的key為T(mén)hreadLocal實(shí)例的引用地址,value存儲(chǔ)真正的變量。
這樣設(shè)計(jì)就到達(dá)目的了。ThreadLocal構(gòu)建時(shí)接收個(gè)泛型告訴你存儲(chǔ)的變量是一個(gè)對(duì)象類(lèi)型。
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } ... }
這里Map的Entry被設(shè)計(jì)為弱引用。
內(nèi)存存儲(chǔ)圖示如下
WeakReference是Java語(yǔ)言規(guī)范中為了區(qū)別直接的對(duì)象引用(程序中通過(guò)構(gòu)造函數(shù)聲明出來(lái)的對(duì)象引用)而定義的另外一種引用關(guān)系。WeakReference標(biāo)志性的特點(diǎn)是:reference實(shí)例不會(huì)影響到被應(yīng)用對(duì)象的GC回收行為(即只要對(duì)象被除WeakReference對(duì)象之外所有的對(duì)象解除引用后,該對(duì)象便可以被GC回收),只不過(guò)在被對(duì)象回收之后,reference實(shí)例想獲得被應(yīng)用的對(duì)象時(shí)程序會(huì)返回null。
這里使用弱引用的原因:ThreadLocal目的就是達(dá)到變量只能自己訪問(wèn)別的線程不能訪問(wèn)的目的,Map設(shè)計(jì)的key為T(mén)hreadLocal實(shí)例的引用地址,value為變量,當(dāng)ThreadLocal定義處的實(shí)例被銷(xiāo)毀時(shí),如果Map中Entity還強(qiáng)引用該實(shí)例時(shí),就會(huì)導(dǎo)致該實(shí)例不會(huì)被回收,而外部因?yàn)橐脤?duì)象被銷(xiāo)毀而不能操作該地址,于是會(huì)造成內(nèi)存泄露。當(dāng)Map的Key設(shè)計(jì)為弱引用時(shí),如果外部實(shí)例被銷(xiāo)毀,即使弱引用還存在,Map中的對(duì)應(yīng)的ThreadLocal也會(huì)被回收,則key=null。
使用弱引用后,雖然Key可以被回收了,但是value還是存在的,還會(huì)存在臨時(shí)內(nèi)存泄露問(wèn)題,圖示如下
臨時(shí)內(nèi)存泄露圖示
對(duì)于臨時(shí)內(nèi)存泄露,ThreadLocal在后續(xù)get、set操作時(shí),會(huì)清理掉Map中key=null的Entity節(jié)點(diǎn),不過(guò)再清理之前Value一直存在,如果繼續(xù)使用Value很容易出現(xiàn)問(wèn)題
ThreadLocal由于一個(gè)實(shí)例存儲(chǔ)一個(gè)對(duì)象,因此一般會(huì)被定義為static的,防止ThreadLocal所在類(lèi)實(shí)例被多次創(chuàng)建時(shí)ThreadLocal也被多次創(chuàng)建,
另外ThreadLocal也會(huì)定義為final的,防止實(shí)例被覆蓋掉,也就是只在實(shí)例化時(shí)new一次
在Thread類(lèi)定義中這樣定義ThreadLocalMap
public class Thread implements Runnable { ... ThreadLocal.ThreadLocalMap threadLocals = null; ... }
也就是ThreadLocalMap是線程類(lèi)Thread持有的對(duì)象,一個(gè)線程持有一個(gè)ThreadLocalMap,只有線程存在,則ThreadLocalMap必存在。這也是ThreadLocal對(duì)象銷(xiāo)毀后,value還存在的原因,ThreadLocalMap還被Thread強(qiáng)引用。只有線程銷(xiāo)毀時(shí),ThreadLocalMap才會(huì)隨之銷(xiāo)毀。
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); // 接收泛型
threadLocal.set(1); // 對(duì)應(yīng)泛型
Integer var=threadLocal.get();
public void set(T value) { // 獲取當(dāng)前線程 Thread t = Thread.currentThread(); // 從當(dāng)前線程中獲取一個(gè)ThreadLocalMap實(shí)例 ThreadLocalMap map = getMap(t); // map存在則放入key,value key為當(dāng)前ThreadLocal對(duì)象引用 if (map != null) map.set(this, value); // map不存在則構(gòu)建,同樣放入key,value else createMap(t, value); }
ThreadLocalMap實(shí)例的獲取就是從Thread中獲取的,也就是上面的持有狀態(tài),拿到之后就可以向Map結(jié)構(gòu)中存儲(chǔ)key,value了,前面也說(shuō)過(guò)了,這里key存在的ThreadLocal實(shí)例的引用地址,value存在的變量的引用地址。一個(gè)線程可以存儲(chǔ)多個(gè)ThreadLocal實(shí)例。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
就是獲取當(dāng)前線程持有的ThreadLocalMap
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
給當(dāng)前線程初始化ThreadLocalMap實(shí)例,并set初始值。key為T(mén)hreadLocal實(shí)例的引用地址
public T get() { Thread t = Thread.currentThread(); //① 獲取當(dāng)前線程t ThreadLocalMap map = getMap(t); //②從當(dāng)前線程中獲取一個(gè)ThreadLocalMap if (map != null) { //③ ThreadLocalMap不為null則從map中獲取value ; ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue();//④ThreadLocalMap為null則調(diào)用setInitialValue() 返回—初始化的值 }
在最后一個(gè)return之前的代碼就是從當(dāng)前線程的ThreadLocalMap中通過(guò)Map的get方法獲取變量引用的過(guò)程。這是Map的基本用用法。
存在以下情況則需要獲取初始的默認(rèn)值,這是一個(gè)對(duì)外開(kāi)放的功能,就是可以指定ThreadLocal對(duì)應(yīng)的變量的初始默認(rèn)值,默認(rèn)為null,可以被重寫(xiě)
ThreadLocalMap未實(shí)例化
ThreadLocalMap已實(shí)例化,但是還沒(méi)有Set變量
protected T initialValue() { return null; } private T setInitialValue() { T value = initialValue(); //① 初始化為null Thread t = Thread.currentThread(); //② 獲取當(dāng)前線程 ThreadLocalMap map = getMap(t); //③ 從當(dāng)前線程中獲取一個(gè)ThreadLocalMap if (map != null) map.set(this, value); //④ ThreadLocalMap不為null,則存儲(chǔ)key=this,value=value else createMap(t, value);//⑤ ThreadLocalMap為null,則為當(dāng)前線程創(chuàng)建一個(gè)ThreadLocalMap并初始化值 return value; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
代碼流程也很簡(jiǎn)單,只是為了功能優(yōu)化了,進(jìn)行的統(tǒng)一封裝
ThreadLocalMap的惰性實(shí)例化
獲取ThreadLocalMap時(shí)需要Thread,而ThreadLocal只是訪問(wèn)控制工具,于是需要打通Thread來(lái)獲取ThreadLocalMap
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
主動(dòng)移除是個(gè)好的處理方式,在不使用變量時(shí)應(yīng)該主動(dòng)移除
到這里ThreadLocal本身的功能已經(jīng)介紹完了,可以理解為變量訪問(wèn)工具,這個(gè)變量的訪問(wèn)被控制到只能當(dāng)前線程有權(quán)限訪問(wèn),其他線程無(wú)權(quán)限。
簡(jiǎn)單示例
package cn.tinyice.demo.thread; import java.util.Random; import java.util.concurrent.TimeUnit; /** * ThreadLocalDemo * * @author Tinyice */ public class ThreadLocalDemo { private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); private static final Random random=new Random(); /** * 內(nèi)存模型示例 */ private int mod=0; public ThreadLocalDemo(int mod) { this.mod = mod; } public int getMod() { return mod; } public void add() { mod+=1; // 每個(gè)線程的值是不一樣的 threadLocal.set(get() + random.nextInt(10)); } public Integer get() { Integer integer = threadLocal.get(); return null == integer ? 0 : integer; } public static void main(String[] args) { ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo(0); for (int i = 0; i < 1000; i++) { new ThreadLocalThread(threadLocalDemo).start(); } try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("------------------------------------------------"); System.out.println(String.format("threadId=%d threadLocal value=%d mod=%d", Thread.currentThread().getId(), threadLocalDemo.get(),threadLocalDemo.getMod())); } } class ThreadLocalThread extends Thread { private ThreadLocalDemo threadLocalDemo; public ThreadLocalThread( ThreadLocalDemo threadLocalDemo) { this.threadLocalDemo = threadLocalDemo; } @Override public void run() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } threadLocalDemo.add(); System.out.println(String.format("threadId=%d threadLocal value=%d", Thread.currentThread().getId(), threadLocalDemo.get())); } }
控制臺(tái):
threadId=853 threadLocal value=3 threadId=848 threadLocal value=9 threadId=808 threadLocal value=9 threadId=121 threadLocal value=8 ------------------------------------------------ threadId=1 threadLocal value=0 mod=999 Process finished with exit code 0
mod=999 是可見(jiàn)性問(wèn)題造成,而value值都不一樣則說(shuō)明不同線程的變量值不一樣。
Spring示例
public abstract class TransactionSynchronizationManager { private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class); private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active"); ... }
public final class DateTimeContextHolder { private static final ThreadLocal<DateTimeContext> dateTimeContextHolder = new NamedThreadLocal<>("DateTimeContext"); ... }
到此,關(guān)于“java中ThreadLocal有什么作用”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
當(dāng)前名稱:java中ThreadLocal有什么作用
文章地址:http://aaarwkj.com/article18/jjhhdp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、建站公司、品牌網(wǎng)站建設(shè)、App設(shè)計(jì)、網(wǎng)站設(shè)計(jì)公司、網(wǎng)站營(yíng)銷(xiāo)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)