日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

Java中ThreadLocal的一些理解

瀏覽:135日期:2022-08-19 17:38:29

前言

面試的時候被問到ThreadLocal的相關知識,沒有回答好(奶奶的,現在感覺問啥都能被問倒),所以我決定先解決這幾次面試中都遇到的高頻問題,把這幾個硬骨頭都能理解的透徹的說出來了,感覺最起碼不能總是一輪游。

ThreadLocal介紹

ThreadLocal是JDK1.2開始就提供的一個用來存儲線程本地變量的類。ThreadLocal中的變量是在每個線程中獨立存在的,當多個線程訪問ThreadLocal中的變量的時候,其實都是訪問的自己當前線程的內存中的變量,從而保證的變量的線程安全。

我們一般在使用ThreadLocal的時候都是為了解決線程中存在的變量競爭問題。其實解決這類問題,通常大家也會想到使用synchronized來加鎖解決。

例如在解決SimpleDateFormat的線程安全的時候。SimpleDateFormat是非線程安全的,它里面無論的是format()方法還是parse()方法,都有使用它自己內部的一個Calendar類的對象,format方法是設置時間,parse()方法里面是先調用Calendar的clear()方法,然后又調用了Calendar的set()方法(賦值),如果一個線程剛調用了set()進行賦值,這個時候又來了一個線程直接調用了clear()方法,那么這個parse()方法執行的結果就會有問題的。

解決辦法一將使用SimpleDateformat的方法加上synchronized,這樣雖然保證了線程安全,但卻降低了效率,同一時間只有一個線程能使用格式化時間的方法。

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat('yyyy-MM-dd HH:mm:ss');public static synchronized String formatDate(Date date){ return simpleDateFormat.format(date);}

解決辦法二將SimpleDateFormat的對象,放到ThreadLocal里面,這樣每個線程中都有一個自己的格式對象的副本了?;ゲ桓蓴_,從而保證了線程安全。

private static final ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat('yyyy-MM-dd HH:mm:ss'));public static String formatDate(Date date){ return simpleDateFormatThreadLocal.get().format(date);}

ThreadLocal的原理

我們先看一下ThreadLocal是怎么使用的。

ThreadLocal<Integer> threadLocal99 = new ThreadLocal<Integer>();threadLocal99.set(3);int num = threadLocal99.get();System.out.println('數字:'+num);threadLocal99.remove();System.out.println('數字Empty:'+threadLocal99.get());

運行結果:

數字:3數字Empty:null

使用起來很簡單,主要是將變量放到ThreadLocal里面,在線程執行過程中就可以取到,當執行完成后在remove掉就可以了,只要沒有調用remove()當前線程在執行過程中都是可以拿到變量數據的。因為是放到了當前執行的線程中,所以ThreadLocal中的變量值只能當前線程來使用,從而保證的了線程安全(當前線程的子線程其實也是可以獲取到的)。

來看一下ThreadLocal的set()方法源碼

public void set(T value) { // 獲取當前線程 Thread t = Thread.currentThread(); // 獲取ThreadLocalMap ThreadLocal.ThreadLocalMap map = getMap(t); // ThreadLocalMap 對象是否為空,不為空則直接將數據放入到ThreadLocalMap中 if (map != null) map.set(this, value); else createMap(t, value); // ThreadLocalMap對象為空,則先創建對象,再賦值。}

我們看到變量都是存放在了ThreadLocalMap這個變量中的。那么ThreadLocalMap又是怎么來的呢?

ThreadLocalMap getMap(Thread t) { return t.threadLocals;}

public class Thread implements Runnable {... .../* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ... ...}

通過上面的源碼,我們發現ThreadLocalMap變量是當前執行線程中的一個變量,所以說,ThreadLocal中存放的數據其實都是放到了當前執行線程中的一個變量里面了。也就是存儲在了當前的線程對象里了,別的線程里面是另一個線程對象了,拿不到其他線程對象中的數據,所以數據自然就隔離開了。

那么ThreadLocalMap是怎么存儲數據的呢?ThreadLocalMap 是ThreadLocal類里的一個內部類,雖然類的名字上帶著Map但卻沒有實現Map接口,只是結構和Map類似而已。

Java中ThreadLocal的一些理解

ThreadLocalMap內部其實是一個Entry數組,Entry是ThreadLocalMap中的一個內部類,繼承自WeakReference,并將ThreadLocal類型的對象設置為了Entry的Key,以及對Key設置成弱引用。ThreadLocalMap的內部數據結構,就大概是這樣的key,value組成的Entry的數組集合。

Java中ThreadLocal的一些理解

和真正的Map還是有區別的,沒有鏈表了,這樣在解決key的hash沖突的時候措施肯定就和HashMap不一樣了。一個線程中是可以創建多個ThreadLocal對象的,多個ThreadLocal對象就會存放多個數據,那么在ThreadLocalMap中就會以數組的形式存放這些數據。我們來看一下具體的ThreadLocalMap的set()方法的源碼

/** * Set the value associated with key. * @param key the thread local object * @param value the value to be set */private void set(ThreadLocal<?> key, Object value) { // We don’t use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; // 定位在數組中的位置 int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // 如果當前位置不為空,并且當前位置的key和傳過來的key相等,那么就會覆蓋當前位置的數據 if (k == key) { e.value = value; return; } // 如果當前位置為空,則初始化一個Entry對象,放到當前位置。 if (k == null) { replaceStaleEntry(key, value, i); return; } } // 如果當前位置不為空,并且當前位置的key也不等于要賦值的key ,那么將去找下一個空位置,直接將數據放到下一個空位置處。 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();}

我們從set()方法中可以看到,處理邏輯有四步。

第一步先根據Threadlocal對象的hashcode和數組長度做與運算獲取數據應該放在當前數組中的位置。 第二步就是判斷當前位置是否為空,為空的話就直接初始化一個Entry對象,放到當前位置。 第三步如果當前位置不為空,而當前位置的Entry中的key和傳過來的key一樣,那么直接覆蓋掉當前位置的數據。 第四步如果當前位置不為空,并且當前位置的Entry中的key和傳過來的key 也不一樣,那么就會去找下一個空位置,然后將數據存放到空位置(數組超過長度后,會執行擴容的);

在get的時候也是類似的邏輯,先通過傳入的ThreadLocal的hashcode獲取在Entry數組中的位置,然后拿當前位置的Entry的Key和傳入的ThreadLocal對比,相等的話,直接把數據返回,如果不相等就去判斷和數組中的下一個值的key是否相等。。。

private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e);}

/** * Version of getEntry method for use when key is not found in * its direct hash slot. * * @param key the thread local object * @param i the table index for key’s hash code * @param e the entry at table[i] * @return the entry associated with key, or null if no such */private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null;}

我們上文一直說,ThreadLocal是保存在單個線程中的數據,每個線程都有自己的數據,但是實際ThreadLocal里面的真正的對象數據,其實是保存在堆里面的,而線程里面只是存儲了對象的引用而已。并且我們在使用的時候通常需要在上一個線程執行的方法的上下文共享ThreadLocal中的變量。例如我的主線程是在某個方法中執行代碼呢,但是這個方法中有一段代碼時新創建了一個線程,在這個線程里面還使用了我這個正在執行的方法里面的定義的ThreadLocal里面的變量。這個時候,就是需要從新線程里面調用外面線程的數據,這個就需要線程間共享了。這種子父線程共享數據的情況,ThreadLocal也是支持的。例如:

ThreadLocal threadLocalMain = new InheritableThreadLocal(); threadLocalMain.set('主線程變量'); Thread t = new Thread() { @Override public void run() { super.run(); System.out.println( '現在獲取的變量是 =' + threadLocalMain.get()); } }; t.start();

運行結果:

現在獲取的變量是 =主線程變量

上面這樣的代碼就能實現子父線程共享數據的情況,重點是使用InheritableThreadLocal來實現的共享。那么它是怎么實現數據共享的呢?在Thread類的init()方法中有這么一段代碼:

if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

這段代碼的意思是,在創建線程的時候,如果當前線程的inheritThreadLocals變量和父線程的inheritThreadLocals變量都不為空的時候,會將父線程的inheritThreadLocals變量中的數據,賦給當前線程中的inheritThreadLocals變量。

ThreadLocal的內存泄漏問題上文我們也提到過,ThreadLocal中的ThreadLocalMap里面的Entry對象是繼承自WeakReference類的,說明Entry的key是一個弱引用。

Java中ThreadLocal的一些理解

弱引用是用來描述那些非必須的對象,弱引用的對象,只能生存到下一次垃圾收集發生為止。當垃圾收集器開始工作,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。

這個弱引用還是ThreadLocal對象本身,所以一般在線程執行完成后,ThreadLocal對象就會變成null了,而為null的弱引用對象,在下一次GC的時候就會被清除掉,這樣Entry的Key的內存空間就被釋放出來了,但是Entry的value還在占用的內存,如果線程是被復用的(例如線程池中的線程),那么這里面的value值就會越來越多,最終就導致了內存泄漏。

防止內存泄漏的辦法就是在每次使用完ThreadLocal的時候都去執行以下remove()方法,就可以把key和value的空間都釋放了。

那既然容易產生內存泄漏,為什么還要設置成弱引用的呢?如果正常情況下應該是強引用,但是強引用只要引用關系還在就一直不會被回收,所以如果線程被復用了,那么Entry中的Key和Value都不會被回收,這樣就造成了Key和Value都會發生內存泄漏了。

以上就是Java中ThreadLocal的一些理解的詳細內容,更多關于Java ThreadLocal的資料請關注好吧啦網其它相關文章!

標簽: Java
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
成人三级高清视频在线看| 欧美亚洲专区| 性感美女一区二区在线观看| 97久久亚洲| 少妇精品久久久一区二区| 久久99免费视频| 中文av在线全新| 久久精品一区二区国产| 国产精品美女久久久久久不卡| 美女黄网久久| 在线国产一区二区| 在线亚洲免费| 国产日韩一区二区三区在线 | 久久精品国产久精国产爱| 国产亚洲一级| 三级欧美在线一区| 日韩高清电影免费| 精品一区视频| 福利精品在线| 999国产精品| 国产亚洲一级| 中文国产一区| 亚洲欧洲日韩精品在线| 欧美日韩夜夜| 日韩精品免费一区二区夜夜嗨| 深夜福利亚洲| 日韩1区2区| 亚洲精品福利| 国产成人精品三级高清久久91| 久久中文字幕av一区二区不卡| 国产精品第一| 国产成人免费av一区二区午夜| 在线视频精品| 久久中文字幕av| 免费看的黄色欧美网站| 国产欧美日韩在线一区二区| 日韩和欧美的一区| 视频二区不卡| 国产亚洲一区| 欧美激情综合| 国产一区二区三区成人欧美日韩在线观看 | 国产一区二区三区成人欧美日韩在线观看| 蜜桃一区二区三区在线| 伊人影院久久| 国产探花在线精品一区二区| 91精品一区二区三区综合在线爱| 久久超级碰碰| 荡女精品导航| 精品久久久久久久| 在线日韩av| 日韩欧美字幕| 奇米狠狠一区二区三区| 爽好久久久欧美精品| 三级小说欧洲区亚洲区| 亚洲免费精品| 久久久9色精品国产一区二区三区| 国产情侣久久| 亚洲精品三级| 狠狠色狠狠色综合日日tαg| 麻豆国产精品| 欧美国产不卡| 日韩精品第一| 日韩三级久久| 日本伊人午夜精品| 国产精品嫩草99av在线| 日本免费一区二区三区四区| 国产成人精品福利| 色婷婷狠狠五月综合天色拍| 精品少妇av| 国产精品成久久久久| 四虎8848精品成人免费网站| 国产精品久久免费视频| 国产精品最新| 国产一区二区三区成人欧美日韩在线观看| 国产一区二区三区黄网站| 国产精品久久久久久久久久齐齐| 国产欧美一区| 国语精品一区| 精品亚洲成人| 精品精品99| a国产在线视频| 亚洲一区二区免费看| 日韩中文字幕区一区有砖一区| 亚洲综合小说| 久久精品 人人爱| 在线看片一区| 国产亚洲人成a在线v网站| 欧美aⅴ一区二区三区视频| 欧美日韩精品免费观看视欧美高清免费大片 | 久久高清一区| 亚洲视频二区| 天堂√中文最新版在线| 你懂的网址国产 欧美| 成人精品高清在线视频| 免费精品国产的网站免费观看| 国产精品一区二区三区美女 | 免费精品国产的网站免费观看| 亚洲三级毛片| 国产一区二区色噜噜| 超碰在线99| 亚洲字幕久久| 欧美日韩国产观看视频| 蜜桃av在线播放| 中文字幕一区二区三区四区久久| 精品三区视频| 国产精品videosex极品| 五月天久久久| 精品视频免费| 亚洲精品在线二区| 免费国产自久久久久三四区久久| 日本va欧美va精品发布| 欧美.日韩.国产.一区.二区 | 综合亚洲视频| 特黄毛片在线观看| 国产精品流白浆在线观看| 视频一区视频二区中文字幕| 日韩av福利| 精品一区二区三区在线观看视频 | 玖玖玖国产精品| 一区二区三区四区日本视频| 欧美日韩精品一区二区三区在线观看| 深夜视频一区二区| 丰满少妇一区| 精品久久久网| 精品国产不卡| 欧美a级一区二区| 欧美xxxx性| 精品视频网站| 国产精品地址| 久久国产免费看| 日韩国产一区二| 久久国内精品自在自线400部| 亚洲激情五月| 午夜在线一区| 视频一区免费在线观看| 国产色播av在线| 欧美视频二区| 国产精品第十页| 福利片在线一区二区| 精品一区电影| 国产精品密蕾丝视频下载| 国产精品一区二区精品视频观看 | 麻豆视频在线观看免费网站黄 | 国产毛片久久| 日韩亚洲国产欧美| 国产精品丝袜xxxxxxx| 亚洲精品护士| 欧美黑人巨大videos精品| 日韩欧美2区| 国产精品亚洲综合在线观看| 三上悠亚国产精品一区二区三区| 午夜国产一区二区| 91精品国产调教在线观看| 久久国产日韩| 最新国产精品| 黑人精品一区| 国产a亚洲精品| 国产视频一区欧美| 久久国产精品美女| 合欧美一区二区三区| 伊人久久亚洲美女图片| 欧美亚洲网站| 欧美~级网站不卡| 亚洲一区二区三区久久久| 亚洲女同中文字幕| 国产精品美女久久久久久不卡| 成人羞羞视频播放网站| 日本综合视频| 国产一区二区三区91| 午夜在线精品偷拍| 久久精品理论片| 欧美日韩国产免费观看视频| 天使萌一区二区三区免费观看| 精品亚洲a∨| 欧美专区一区| 视频一区视频二区在线观看| 日韩精品五月天| 米奇777超碰欧美日韩亚洲| 国产精品第一| 午夜性色一区二区三区免费视频| 日韩福利在线观看| 午夜国产精品视频| 婷婷激情一区| 红杏一区二区三区| 激情五月综合| 日韩一区亚洲二区| 日韩中文字幕视频网| 中文一区在线| 国产成人精品一区二区三区在线| 青草综合视频| 亚洲日韩中文字幕一区| 蜜桃一区二区三区在线| 99视频一区| 国产精品88久久久久久| 欧美片第1页| 91精品国产91久久久久久黑人| 国产suv精品一区二区四区视频| 日韩精品a在线观看91| 日本色综合中文字幕| 国产区精品区|