這篇文章主要介紹“java中ThreadLocal有什么作用”,在日常操作中,相信很多人在java中ThreadLocal有什么作用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”java中ThreadLocal有什么作用”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
創(chuàng)新互聯(lián)是一家集網(wǎng)站建設(shè),故城企業(yè)網(wǎng)站建設(shè),故城品牌網(wǎng)站建設(shè),網(wǎng)站定制,故城網(wǎng)站建設(shè)報價,網(wǎng)絡(luò)營銷,網(wǎng)絡(luò)優(yōu)化,故城網(wǎng)站推廣為一體的創(chuàng)新建站企業(yè),幫助傳統(tǒng)企業(yè)提升企業(yè)形象加強企業(yè)競爭力??沙浞譂M足這一群體相比中小企業(yè)更為豐富、高端、多元的互聯(lián)網(wǎng)需求。同時我們時刻保持專業(yè)、時尚、前沿,時刻以成就客戶成長自我,堅持不斷學(xué)習(xí)、思考、沉淀、凈化自己,讓我們?yōu)楦嗟钠髽I(yè)打造出實用型網(wǎng)站。
內(nèi)存模型中所有變量存儲在主內(nèi)存中,當(dāng)一個線程中要使用某個變量時,需要從主內(nèi)存復(fù)制該變量到其線程內(nèi)才能操作,此時線程中操作的是主內(nèi)存變量的副本,操作完成后再刷回主內(nèi)存。刷回的實質(zhì)就是變量賦值
如果多個線程訪問同一個變量時,每個線程都具有一個副本,操作完畢后都會刷回主內(nèi)存,刷回時間存在先后,則賦值有先后,當(dāng)然后者會覆蓋前者,這是造成可見性問題的次要原因。
引入以上知識點后,再來說明ThreadLocal。一個線程想使用某個變量,于是從主內(nèi)存復(fù)制該共享變量到線程內(nèi)部中。使用完畢后想再下次再次使用該變量時,得到的變量副本是上次使用的副本,而不是從主內(nèi)存的變量再次復(fù)制過來的副本,并且不想讓其他線程影響到該變量。這就是ThreadLocal的目的,其實現(xiàn)不是通過共享變量這種方式實現(xiàn)的,詳細(xì)內(nèi)容下面介紹
目的很明確,但是身處JAVA內(nèi)存模型中要遵循內(nèi)存模型規(guī)范,下面看看JDK是如何即滿足內(nèi)存模型規(guī)范,又滿足ThreadLocal目的。
這點很簡單,就是你該怎么樣還怎么樣,仍然受你管轄,該復(fù)制就復(fù)制,該刷回就刷回,不可見還是會造成不可見。
多個線程都能訪問的變量才叫共享變量,如果控制變量的訪問方式,使其他線程線程不能訪問就可以了??刂品绞骄褪菍⒕€程與變量的一一對應(yīng),將該變量的訪問入口控制到只有該線程即可,JDK中的做法就是讓線程持有這個變量(綁定到線程本身)
線程可以綁定變量,但是并不知道需要綁定多少個,于是將這個存儲功能還是交給專門的數(shù)據(jù)結(jié)構(gòu)—>Map。并且還專門設(shè)計了一個用來訪問這個Map的工具,這個工具就是ThreadLocal。并且這個Map的key為ThreadLocal實例的引用地址,value存儲真正的變量。
這樣設(shè)計就到達目的了。ThreadLocal構(gòu)建時接收個泛型告訴你存儲的變量是一個對象類型。
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è)計為弱引用。
內(nèi)存存儲圖示如下
WeakReference是Java語言規(guī)范中為了區(qū)別直接的對象引用(程序中通過構(gòu)造函數(shù)聲明出來的對象引用)而定義的另外一種引用關(guān)系。WeakReference標(biāo)志性的特點是:reference實例不會影響到被應(yīng)用對象的GC回收行為(即只要對象被除WeakReference對象之外所有的對象解除引用后,該對象便可以被GC回收),只不過在被對象回收之后,reference實例想獲得被應(yīng)用的對象時程序會返回null。
這里使用弱引用的原因:ThreadLocal目的就是達到變量只能自己訪問別的線程不能訪問的目的,Map設(shè)計的key為ThreadLocal實例的引用地址,value為變量,當(dāng)ThreadLocal定義處的實例被銷毀時,如果Map中Entity還強引用該實例時,就會導(dǎo)致該實例不會被回收,而外部因為引用對象被銷毀而不能操作該地址,于是會造成內(nèi)存泄露。當(dāng)Map的Key設(shè)計為弱引用時,如果外部實例被銷毀,即使弱引用還存在,Map中的對應(yīng)的ThreadLocal也會被回收,則key=null。
使用弱引用后,雖然Key可以被回收了,但是value還是存在的,還會存在臨時內(nèi)存泄露問題,圖示如下
臨時內(nèi)存泄露圖示
對于臨時內(nèi)存泄露,ThreadLocal在后續(xù)get、set操作時,會清理掉Map中key=null的Entity節(jié)點,不過再清理之前Value一直存在,如果繼續(xù)使用Value很容易出現(xiàn)問題
ThreadLocal由于一個實例存儲一個對象,因此一般會被定義為static的,防止ThreadLocal所在類實例被多次創(chuàng)建時ThreadLocal也被多次創(chuàng)建,
另外ThreadLocal也會定義為final的,防止實例被覆蓋掉,也就是只在實例化時new一次
在Thread類定義中這樣定義ThreadLocalMap
public class Thread implements Runnable { ... ThreadLocal.ThreadLocalMap threadLocals = null; ... }
也就是ThreadLocalMap是線程類Thread持有的對象,一個線程持有一個ThreadLocalMap,只有線程存在,則ThreadLocalMap必存在。這也是ThreadLocal對象銷毀后,value還存在的原因,ThreadLocalMap還被Thread強引用。只有線程銷毀時,ThreadLocalMap才會隨之銷毀。
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); // 接收泛型
threadLocal.set(1); // 對應(yīng)泛型
Integer var=threadLocal.get();
public void set(T value) { // 獲取當(dāng)前線程 Thread t = Thread.currentThread(); // 從當(dāng)前線程中獲取一個ThreadLocalMap實例 ThreadLocalMap map = getMap(t); // map存在則放入key,value key為當(dāng)前ThreadLocal對象引用 if (map != null) map.set(this, value); // map不存在則構(gòu)建,同樣放入key,value else createMap(t, value); }
ThreadLocalMap實例的獲取就是從Thread中獲取的,也就是上面的持有狀態(tài),拿到之后就可以向Map結(jié)構(gòu)中存儲key,value了,前面也說過了,這里key存在的ThreadLocal實例的引用地址,value存在的變量的引用地址。一個線程可以存儲多個ThreadLocal實例。
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實例,并set初始值。key為ThreadLocal實例的引用地址
public T get() { Thread t = Thread.currentThread(); //① 獲取當(dāng)前線程t ThreadLocalMap map = getMap(t); //②從當(dāng)前線程中獲取一個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() 返回—初始化的值 }
在最后一個return之前的代碼就是從當(dāng)前線程的ThreadLocalMap中通過Map的get方法獲取變量引用的過程。這是Map的基本用用法。
存在以下情況則需要獲取初始的默認(rèn)值,這是一個對外開放的功能,就是可以指定ThreadLocal對應(yīng)的變量的初始默認(rèn)值,默認(rèn)為null,可以被重寫
ThreadLocalMap未實例化
ThreadLocalMap已實例化,但是還沒有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)前線程中獲取一個ThreadLocalMap if (map != null) map.set(this, value); //④ ThreadLocalMap不為null,則存儲key=this,value=value else createMap(t, value);//⑤ ThreadLocalMap為null,則為當(dāng)前線程創(chuàng)建一個ThreadLocalMap并初始化值 return value; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
代碼流程也很簡單,只是為了功能優(yōu)化了,進行的統(tǒng)一封裝
ThreadLocalMap的惰性實例化
獲取ThreadLocalMap時需要Thread,而ThreadLocal只是訪問控制工具,于是需要打通Thread來獲取ThreadLocalMap
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
主動移除是個好的處理方式,在不使用變量時應(yīng)該主動移除
到這里ThreadLocal本身的功能已經(jīng)介紹完了,可以理解為變量訪問工具,這個變量的訪問被控制到只能當(dāng)前線程有權(quán)限訪問,其他線程無權(quá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; // 每個線程的值是不一樣的 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())); } }
控制臺:
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 是可見性問題造成,而value值都不一樣則說明不同線程的變量值不一樣。
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é)束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
當(dāng)前名稱:java中ThreadLocal有什么作用
文章地址:http://jinyejixie.com/article18/jjhhdp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、建站公司、品牌網(wǎng)站建設(shè)、App設(shè)計、網(wǎng)站設(shè)計公司、網(wǎng)站營銷
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)