多线程进阶
1. 進程與線程
進程:是操作系統中的各個程序,是資源分配的基本單位,舉例:QQ.exe,分配內存、端口號
線程:是執行程序的基本單位,也就是說程序中的代碼是由線程執行的,一個進程中包含一個或多個線程,可以并行執行。
進程C
進程A
進程B
線程aO
線程cO
線程aO
線程a
線程bO
線程aO
線程bO
線程bO
線程b
操作系統
CPU1
CPU2
1. 多線程的重要性?
多線程是基礎,基礎到什么程度?基礎到如果不會多線程,那么最簡單的CRUD都寫不好。
具個例子:現在有一個Person類,有name屬性,保存時要求name唯一,
這時如果兩個用戶同時提交且名字一樣,那么后臺就會有兩個線程同時執行 create 方法,最終導致數據庫存入兩條重復的數據。
或許你會說在數據庫表中增加唯一約束,但是如果該表的數據只能邏輯刪除,這樣就是有問題的。
所以,只有學好多線程、鎖,這些東西,才能寫好代碼。
2. 創建線程的兩種方式
為什么寫了4種?
因為網上有種說法,把 Callable和線程池 各自列為一種創建線程的方式,
但是我們需要知道其實它們的在本質上都是第二種
3. 線程狀態
該知識點,了解就行,Thread類中有State枚舉
初始
NEW
Thread.sleep(long)
Thread.starto
Object.wait(long)
Object.waito
運行
Thread.join(long)
objectjoino
RUNNABLE)
LockSupport.parkNanoso
LockSupport.parko
運行中
LockSupport.parkUntilo
RUNNING)
等待
超時等待
yieldo
WAITING)
系統調度
(TIMEDWAITING)
系統調度
object.notifyo
Object.notifyo
就緒(READY
Object.notifyAlo
Object.notifyAio
LockSupport.unpark(Thread)
LockSupport.unpark(Thread)
超進時間到
等待進入synchronized方法
等待進入synchronized塊
獲取到鎖
執行完成
阻寒
終止
BLOCKED)
(TERMINATED)
1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。
2. 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱為“運行”。
線程創建后,且調用了start()方法。該狀態的線程位于可運行線程池中,等待獲取CPU的使用權,
此時處于就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片后變為運行中狀態(running)。
3. 阻塞(BLOCKED):表示線程等待獲取鎖。
4. 等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(喚醒或打斷)。
5. 超時等待(TIMED_WAITING):跟WAITING不同,可在指定時間后自己運行,如果等待時釋放鎖,超時后阻塞于鎖。
6. 終止(TERMINATED):表示該線程已經執行完畢。
狀態切換時常用方法
start() | 啟動一個線程 |
run() | 線程需要執行的代碼,run方法結束,該線程結束 |
sleep(long millis) | 線程休眠,但不釋放鎖 |
join() | 等待該線程結束,可以讓線程順序執行 |
wait()/notify()/notifyAll() | wait()使當前線程等待,前提是 必須先獲得鎖,一般配合synchronized 關鍵字使用 只有當 notify/notifyAll() 被執行時候,才會喚醒該線程繼續執行,直到執行完synchronized 代碼塊或是再次遇到wait() notify/notifyAll() 的執行只是喚醒等待的線程,而不會立即釋放鎖,鎖的釋放要看代碼塊的具體執行情況。所以盡量在使用了notify/notifyAll() 后立即退出臨界區,以喚醒其他線程讓其獲得鎖 |
代碼演示
特殊情況下的notify
4. 一些常見問題
1. 多個線程順序執行
●使用join方法
●定義一個共享變量,各個線程根據變量執行
2. 停止線程
停止線程的最好方式是讓線程正常結束
●聲明一個變量,設置一個開關
●interrupt方法,interrupt()并不會終止線程!只是將線程的中斷標記設為true
○如果線程在阻塞、睡眠、等待,會拋出InterruptedException
5. synchronized
1. 字符串加鎖
不建議對字符串加鎖,字符串比較特殊,一般情況下在內存中只有一份兒,兩個線程分別對同一個字符串加鎖,非常容易產生阻塞,甚至是死鎖。而且如果用法不對,加鎖毫無效果。
2. 鎖升級過程
待完善
簡單過程
●第一次加鎖,偏向鎖,記錄Thread Id
●另一個線程來,發現Thread Id 不同,鎖升級:輕量級鎖,又稱 自旋鎖
●自旋一定次數后仍然搶不到鎖,升級為重量級鎖,線程掛起,等待
JaraSychrontzod原理
Blog.dreamtobe.cn
康星顏鎮
輕量級鎖
就撐葛阿網步代碼球
目新褲狀變
婚持有省雞鎖購蛙程購轉中分配領記
詩新電購理中牙配鎖記嬰
0108克包
旗新旺甲
oA
星品查的話
持貝對常失中藥MtWord有桂程區展中
烤美對尖中拌心意Word當記
cAS鞋作
首致Threas
哈宣時象尖美NAXRWTOrO中記素的
商貴試
糯象失隆M金KWor中尼泰
羅持有智網休未程慶得控鎖飯鎖
慶瑞
預肉蘭標轉程鎖記帖
德南楠祥有皇肉鎖程鎖記的理計電惠:00
成武萄療有會雞鎮購轉摩
特變力童選設鎖
淘自童皇想統炸升惠板加
原持有售肉鎖轉甲
原邦有信向鎖的情程劑達安會店
TMONGLLEPOEHLY1西籃肉1志
營牛酒蚌有省肉鎖購連程
升姨大性蟹德鎖
楓行胃代碼特
怡查湖冷有省向特價機
周商當新桂程使記最的指什兒特惠食館
未活動楓泰/已漂出網步代碼塊
鞋國被鞋您門超強鎮司
開始斯-輪值茂手
腦欖博有盒方評的桂程
特
肉習磚檔20n爵針
日煲
餐園德鋪
從安全點境傳膚行
開始鞋園速餐新樓
1時牽頭中容MW中2萄量3G指身轉程錢記
oL霞看省育話
腳在海粉鎖記餐摩中W
武婆否省尚肉
婚越鎖
g.dreamtobe.cn
輕量級和重量級鎖的使用,輕量級自旋時很消耗cpu
如果線程數少,而且運行速度較快,適合輕量級鎖,反之使用重量級鎖
2. 線程3個特性
1. 可見性
在java中,每一個線程都有一塊工作內存,其中存放著主內存中的變量值得拷貝,當線程執行時,它在自己的工作內存區中操作這些變量。
JMM(JAVA內存模型)
線程
線程
線程
線程
工作內存行1
工作內存
工作內存
工作內存
副本
副本
副本
副本
主內存(變量a)
代碼:
volatile 使變量在多個線程中可見,當一個線程修改變量后,強制其他線程到主內存中讀取變量值,性能比synchronized強,不會阻塞。但是不具備原子性,不適當的使用,在CPU層面上極有可能造成計算速度降低。
代碼:
以上代碼運行時間:220毫秒左右,
對代碼進行修改,把變量a加上 volatile,運行時間:3000毫秒左右。
想知道原因,得先弄明白以下幾個東西:
CPU緩存
工作內存 本質上就是 CPU緩存 ,是 CPU與內存之間的臨時數據區
CPU
主內存
CPU
緩存
為什么需要CPU緩存?
●解決CPU運行速度與內存讀寫速度不匹配的矛盾——緩存的速度比內存的速度快多了。
●CPU往往需要重復處理相同的數據、重復執行相同的指令,如果這部分數據、指令CPU能在CPU緩存中找到,CPU就不需要從內存或硬盤中再讀取數據、指令,從而提高運行速度。
CPU緩存分為3級:L1一級緩存、L2二級緩存、L3三級緩存,它們的作用都是作為CPU與主內存之間的高速數據緩沖區,L1最靠近CPU核心;L2其次;L3再次。
CPU
核心1
核心2
L1
L1
L1
數據緩存
指令緩存
數據緩存
指令緩存
L2緩存
L2緩存
L3緩存
速度方面:L1最快、L2次快、L3最慢;
大小方面:L1最小、L2較大、L3最大。
LO:
寄存器
CPU寄存器保存著從高速
更小
緩存存儲器取出的字
LI
更快和
LI:
高速緩存
一
(每字節)
(SRAM)
LI高速緩存保存著從L.2
成本更高的
高速緩存取出的緩存行
L2
L.2:
存儲設備
高速緩存
L2高速緩存保存著從L3
SRAM)
高速緩存取出的級存行
L3
L3:
高速緩存
(SRAM)
L3高速緩存保存著從主存
高速緩存取出的緩存行
更大
LA:
主存(DRAM)
更慢和
主存保存著從本地磁盤
(每字節)
取出的磁盤塊
差別更低的
L5
本地二級存儲(本地磁盤)
存儲設備
本地磁盤保存著從遠程網絡
服務器磁盤上取出的文件
遠程二級存儲
L6:
(分布式文件系統,Wcb服務器)
CPU會先在L1中尋找需要的數據,找不到再去L2,還找不到再去L3,L3都沒有那就只能去主內存找了。
一級緩存其實還分為一級數據緩存(Data Cache,L1d-Cache)和一級指令緩存(Instruction Cache,l1i-Cache),分別用于存放數據及指令,兩者可同時被CPU訪問,減少了CPU多核心、多線程爭用緩存造成的沖突,提高處理器性能。
緩存行(Cache Line)
CPU從主內存中加載數據到緩存,是以行為單位的,一行64字節,CPU每次從主存中拉取數據時,會把相鄰的數據也存入同一個cache line。也就是說,如果CPU計算時需要用到變量a,假設a是long類型,8字節,那么CPU會把a左右挨著的變量共64字節,統統拿過去。
CACHE LINE
CPU
CACHELINE
CPU
緩存
CACHE LINE
上面代碼加了volatile后,速度變慢的原因分析:
數組是一塊連續內存,并且我們的數組有兩個Demo對象,一共16字節,所以兩個線程使用加載時,極有可能在同一cache line,,都在自己的工作內存中有兩個Demo對象的副本,雖然線程1操作arr[0],線程2操作arr[1],看似互不影響,但是由于變量都是volatile的,當線程1修改a變量時,線程2中的arr[0].a就無效了,這就導致整個cache line無效,所以每次都要從主內存中取數據。
內存
arr
Demo1(a)
Demo2(a)
線程1
線程2
工作內存
工作內存
Demo2a)
Demoia)
Demo2(a)
Demo1a)
對代碼進行修改:
這樣變量代碼中數據的兩個對象,就不可能在同一cache line,所以兩個線程互不影響,再次運行:700毫秒左右。
著名的disruptor框架,也是聲明很多long類型變量:
kagecom.1maxdisupto
importcom.1max.disupto.odclyp
PBNAINBUERXTENDRINGBUEERFLEIDBE
publicstaticTALC-
protectedlongpl;
1ongP2;
Protected
ProtectedlongP3:
ProtectedlongP4;
protectedlongp5;
protectedlongp6;
ProtectedlongP7;
packagecom.1max.disruptori
abstractClassRingBuferPad
Protectedlongpl;
protectedlongP2;
ProtectedlongP3:
protectedlongp4;
Protectedlongp5;
protectedlongp6;
protectedlongP7;
RingBuFferpado
2. 有序性
我們都知道java代碼是逐句執行的,看以下代碼:
上面代碼,code1在code2之前,但是JVM在真正執行這段代碼的時候并不保證code1一定會在code2前面執行,因為這里可能會發生指令重排序。
以下僅做了解:
計算機在執行程序時,為了提高性能,編譯器和處理器常常會對指令重排:
源代碼 -> 編譯器優化的重排 -> 指令并行的重排 -> 內存系統的重排 -> 最終執行指令
●編譯器優化的重排序:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執行順序。
●指令級并行的重排序:現代處理器采用了指令級并行技術來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變語句對應機器指令的執行順序。
●內存系統的重排序:由于處理器使用緩存和讀/寫緩沖區,這使得加載和存儲操作看上去可能是在亂序執行。
雖然不能保證執行順序跟代碼順序一致,但它保證單線程下程序最終執行結果和代碼順序執行的結果是一致的。以上代碼,code1和code2哪個先執行并不影響整個方法的執行結果。
在Java內存模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單線程的執行結果,卻會影響到多線程并發執行的結果。
在多線程情況下:
可以通過synchronized和Lock來保證有序性,也可以通過volatile關鍵字來保證一定的“有序性”。
單例----DCL(Double Check Lock)
以上代碼,在高并發情況先就有可能產生問題。問題在于:instance = new Singleton();這句代碼。
這條語句實際上包含了三條指令:
memory =allocate(); //1:分配對象的內存空間
ctorInstance(memory); //2:初始化對象
instance =memory; //3:設置instance指向剛分配的內存地址
其中2、3的執行順序是可以互換的。
線程B
線程A
1.分配對象的
內存空間
判斷instance是
3.設置instance
否為null
指向內存空間
線程B初次訪問
對象
2.初始化對象
用volatile修飾instance變量,就可以禁止2和3重排序。
為什么volatile可以禁止指令重排序?
通過提供“內存屏障”的方式來防止指令被重排序,為了實現volatile的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障。
內存屏障的概念,不用理解,可以不用看
內存屏障:針對跨處理器的讀寫操作,它被插入到兩個指令之間,作用是禁止編譯器和處理器重排序
按可見性可以分為兩類:加載屏障(Load Barrier)和存儲屏障(Store Barrier)。
加載屏障:刷新處理器緩存。
存儲屏障:沖刷處理器緩存。
JVM會在MonitorEnter(申請鎖)對應的機器碼指令后面,臨界區代碼開始之前插入Load Barrier,以達到臨界區內部使用的共享變量都是新值的作用。同樣,會在MonitorExit(釋放鎖)對應的指令后插入Store Barrier,以達到臨界區對共享變量的更改及時寫回主存。
按有序性劃分:分為獲取屏障(Acquire Barrier)和釋放屏障(Release Barrier)。
獲取屏障:在一個讀操作之后插入一個屏障,禁止與之后的任何讀寫操作重排
釋放屏障:在一個寫操作之前插入一個屏障,禁止與其前面任何讀寫操作重排
內存屏障有4種:
LoadLoad屏障:對于這樣的語句Load1; LoadLoad; Load2,在Load2及后續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。
StoreStore屏障:對于這樣的語句Store1; StoreStore; Store2,在Store2及后續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。
LoadStore屏障:對于這樣的語句Load1; LoadStore; Store2,在Store2及后續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。
StoreLoad屏障:對于這樣的語句Store1; StoreLoad; Load2,在Load2及后續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種內存屏障的功能
volatile的內存屏障策略非常嚴格保守,非常悲觀且毫無安全感的心態:
在每個volatile寫操作前插入StoreStore屏障,在寫操作后插入StoreLoad屏障;在每個volatile讀操作前插入LoadLoad屏障,在讀操作后插入LoadStore屏障;
由于內存屏障的作用,避免了volatile變量和其它指令重排序、線程之間實現了通信,使得volatile表現出了鎖的特性。
但并不是所有代碼都能重排序:
以上代碼,code3和code4的執行順序就不能互換。因為重排序時是會考慮指令之間的數據依賴性。
什么是happen-before(這個概念沒什么不用看)
JMM可以通過happens-before關系向程序員提供跨線程的內存可見性保證(如果A線程的寫操作a與B線程的讀操作b之間存在happens-before關系,盡管a操作和b操作在不同的線程中執行,但JMM向程序員保證a操作將對b操作可見)。
具體的定義為:
1)如果一個操作happens-before另一個依賴操作,那么第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
2)兩個操作之間存在happens-before關系,并不意味著Java平臺的具體實現必須要按照happens-before關系指定的順序來執行。如果重排序之后的執行結果,與按happens-before關系來執行的結果一致,那么這種重排序并不非法(也就是說,JMM允許這種重排序)。
具體的規則:
(1)程序順序規則:一個線程中的每個操作,happens-before于該線程中的任意后續操作。
(2)監視器鎖規則:對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖。
(3)volatile變量規則:對一個volatile域的寫,happens-before于任意后續對這個volatile域的讀。
(4)傳遞性:如果A happens-before B,且B happens-before C,那么A happens-before C。
(5)start()規則:如果線程A執行操作ThreadB.start()(啟動線程B),那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作。
(6)Join()規則:如果線程A執行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。
(7)程序中斷規則:對線程interrupted()方法的調用先行于被中斷線程的代碼檢測到中斷時間的發生。
(8)對象finalize規則:一個對象的初始化完成(構造函數執行結束)先行于發生它的finalize()方法的開始。
3. 原子性
程序的原子性指整個程序中的所有操作,要么全部完成,要么全部不完成,不可能停滯在中間某個環節,有著“同生共死”的感覺。對線程而言,一個操作一旦開始,就不會被其他線程所干擾。
java中必須借助于synchronized、Lock、鎖等,來保證整塊代碼的原子性
3. CAS
流程圖
下面的2,3這兩步,對于操作系統來說是一條指令
不同
A.INCREMENTANDGET();中有CAS操作
是原子操作,不可被打斷
3.NEWVAL+1
1.從內存獲取A的值:NEWVAL
2.NEWVAL 跟A比較
A.INCREMENTANDGET();
相同
第2步,中如果判斷出不等,重新走第1步,這個過程叫做自旋
所以,CAS也稱為自旋鎖
壞處:如果大量線程同時修改一個變量,會導致很多線程不停的自
旋,自旋很占用CPU,這樣性能不好
LongAdder
流程圖
newLongAddero:
LongAddera
base
客戶端
Cell數組,初始長度為2,每次擴容都是變為原來的2倍
cell
cell
自動分段遷移:
如果某個cell執行CAS失敗了,那么就會自動去找另外一個Cel進行CAS操作
ABA問題:
int a =1;
1. 線程1 獲取a的最新值后,準備CAS操作,但這時線程暫停
2. 線程2 修改a的值為2
3. 線程3 又修改a的值為1
4. 線程1 繼續運行,修改a的值。
這種情況下,線程1 雖然可以修改成功,但是這個a已經不是最初的a了,中間經歷的一些變化,如果修改成功,可能
導致一些問題,這就是ABA問題。
解決:加時間戳或版本號
ABA問題:
inta-1:
1.線程1獲取a的最新值后,準備CAS操作,但這時線程暫停
2.線程2修改a的值為2
3.線程3又修改a的值為1
4.線程1繼續運行,修改a的值.
這種情況下,線程1雖然可以修改成功,但是這個已經不是最初的a了,中間經歷的一些變化,
如果修改成功,可能導致一些問題,這就是ABA問題.
解決:加時間蜜或版本號
a三2
a三1
a三1
a三1
CASa-1
CAS
CASa-2
線程1
線程3
線程2
4. Lock
1. 基礎概念
2. 基礎方法
Condition
3. 公平鎖和非公平鎖
4. ReadWriteLock
4. ReentrantLock 原理
AQS AbstractQueuedSynchronizer
抽象隊列同步器:AQS內部有一個核心的變量state,int類型,代表加鎖的狀態。初始值是0。還有一個關鍵變量thread,用來記錄當前加鎖的是哪個線程,初始值是null
過程:
線程a 調用lock()方法進行加鎖,就是用CAS將state值從0變為1,然后設置 thread=線程a
ReentrantLock是一個可重入鎖,每次線程a 再次加鎖就是把state的值給累加 1,別的沒啥變化
這時,線程b 進來發現state已經不是0了,且“加鎖線程”不是自己,所以加鎖失敗。
線程b 會將自己放入AQS中的一個等待隊列,等線程a 釋放鎖之后,重新嘗試加鎖
線程a 在執行完自己的業務邏輯代碼之后,就會釋放鎖!
就是將state變量的值遞減1,等state值為0,則徹底釋放鎖,會將“加鎖線程”變量也設置為null
AQS:內部定義了獲取和釋放鎖的抽象方法,由子類具體實現,FairSync和NonfairSync都實現了這倆方法,這是模板方法模式
protectedbooleantrycquire(intarg)
wUNsupportedoperationException
throw
newUn
**
北ate
the
etoreflectareleaseinexclusive
Attemptstoset
mode.
Thismethodisawaysvkdhhdmng
*
*
p>Thedefaultimplementationthrows
(@linkUnsupportedoperationException
*
*
*
paramargthereleaserqumentTahe
*
passedtoareleasemethodhueuu
entrytoaconditionwaitus
*
uninterpretedandcanrepresentanythingyuke
*
codetruelifthisobjectisnowiu
ereturn
*
statesothatanymtinh
andt@codefalseotherwise.
*
@throws
II1egaiMonitorstateExceptioni
onifreleasingwouldplacethis
*
*
synchronzerinanegstem
*
thzowninaconsstentashionorsynchonztion
correctly.
*
*
unsupportedoperationxceptionu
@throws
protectedbooleantryRelease(itrg)
thrownewUnsupportedoperationxcetin
1. AQS內部變量
AQS中等待隊列是先進先出,本質是鏈表,下面變量都在:AbstractQueuedSynchronizer
2. ReentrantLock 源碼
addWaiter:其實就是將線程放入同步隊列
acquireQueued
3. ReentrantLock 流程
加鎖
CAS加鎖
開始
入隊
加鎖線程是否是本線程
香
state--0
失敗
獲取state
成功
設置exclusiveOynerThread為本線程
結束
state+1
入隊
1.利用CAS設置head-newNodeo
2.tail-head
tail-null
開始
tailanull
eng
3.繼續下一次循環
入隊
1.當前線程節點(node)的prev屬性指向pred(其實就是tail)
2.利用CAS讓tailnode
3.讓之前尾節點的next屬性指向node
阻塞
1.判斷當前node的prev是不是head
需要注意:
只有設置當前node的prev的waitstatus--1
2.執行tryAcquire
當前node才會阻塞
開始
嘗試獲取鎖
當前nod阻寨
失敗
成功
1.設置head-當前node
2.設置之前head的next-null
5. synchronized 和 Lock區別
4. 其他同步方式
1. CountDownLatch
2. CyclicBarrier
3. Semaphore
5. ThreadLocal
1. 簡單使用
2. 使用場景
3. ThreadLocalMap結構
ThreadLocal 本質是操作當前線程的 theadlocals 屬性,它是 ThreadLocalMap 類型
ThreadLocalMap結構:存儲 Entry 數組,數組中的元素有兩個屬性:referent、value
●referent :是 ThreadLocal 對象
●value:自己調用 ThreadLocal 的 set 方法,設置的值
Threadlocal本質操作的是當前線程對象的threadLocals屬性
threadLocals是ThreadLocalmap類型
Entry數組
referent:ThreadLocal對象
referent:Threadlocal對象
value:自己設置的值
value:自己設置的值
TheadLocal跟threadLocals的關系
12
ThreadLocal0:
staticThreadLocal
myThreadLocalznew
13
plicstaticvodmin
15
Threadt-newThread(0->
16
yThreadLocal.set("feng"//這時,"eng"跟當
"跟當前線程關聯,屬于當前線程的私有變量,只能在當前線程中獲取到
17
18
"+myThreadLocal.getO);
System.out.printin(Ted
getName
ThreadLocalo1main020-21.
工山川貓美
Variables
oOThread.currentThreado-hread@657)"ThreadThread-0.main
staticmembersofThreadLocalo1
omyThreadLocalEThreadLocal@672
這時候可以看到myThreadLocal變量指向的對象是672
查看當前線程的threadLocals屬性
土
Variables
Thread.currentThreadohread@657)"ThreadThread-0.5mai
nameBTnreao-U
fpriority-5
fthreadQ-null
eetoP-528824320
single_step-false
眉
daemon-false
00
stillborn-false
ftarC
targethreadLocaloislambda@674
group三fThreadGoup@658Javalang.hdupeminmxpr1
Pade
contextClassLoaderLaunchesAppclassod@659
inheritedAccessContolcontextccesscontlconet@7
threadLocals二ThreadLocalsThreadLocalMap@67)
table二ThreadLocalsThreadLocalmapsEntry(1@79
Notshowingnullelements
10-ThreadLocalsThreadLocalMapsentry@680)
fvalue-"feng"
freferent-ThreadLocal@672)
queue-ReferenceQueuesNull@682
nextanull
discovered二null
SIZeBT
threshold-10
inheritableThreadLocals二null
starkSize80
EventLo
Build
可以看到,最里層有個referent屬性也是指向了672
其實set本質:把myThreadLocal對象和feng,封裝成一個Entry對象,然后放到當前線程的threadLocals屬性中
4. 原理
5. 引用類型
6. 內存泄漏
把myThreadLocal設置為null后,可以看到referent沒有什么變化
ThreadHelper.slegp(ms:2000)
19
20
wyThreadLocai-nuli;
21
System.gc):
22
System.out.printin(myThreadLoca1)
23
):
24
t.start0:
ThreadLocal01main020->1..9
廣山業園美
Variables
threadLocals二ChreadLocalsThreadLocalMap@677
table二ChreadLocalsThreadlocalMapsentry[16]@680)
Notshowingnullelements
10-ThreadLocalsThreadLocalMapsentry@681
value-"feng"
reterentThreadLocal@683
queyeRReferenceQueue$Null@684
nextanull
discovered二null
size二1
threshold-10
finheritableThreadLocalsenull
但是在執行 System.gc(); 這句代碼后
20
myThreadLocaionuil;
System.gcO:
System.out.printn(ThreadLoca
):
tstart0:
ThreadLocalo1)main0>0~>f..
身當
Variables
THreadLocalsThreadLocalMap@677)
threadLocals
ftable二CThreadLocalSsThreadlocalMapsentry[16]@680!
Notshowingnullelements
10-ChreadLocalsThreadLocalMapsEntry@681)
value二"feng"
referent二null
queue-ReferenceQueuesNull@684)
nextThreadLocalsThreadLocalMapsEntry@681
discovered-null
fsize-1
fthreshold-10
inheritableThreadLocals二null
stacksize30
nativeParkEventPointer二0
referent變成null,那么這時候,feng這個值,就再也拿不到了
當然,當前線程結束后feng這個值還是會被回收的
6. 阻塞隊列
1. 整體介紹
Thread2
Thread1
BlockingQueue
Put
Take
一個BlockingQueue,其中一個線程放入其中,另一個線程從中取出.
https://blog.csdn.neuiengxiaoshi
2. ArrayBlockingQueue
3. LinkedBlockingQueue
4. PriorityBlockingQueue
5. DelayQueue
Java復制代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* DelayQueue
* 延遲獲取的無界隊列,添加的元素必須實現 Delayed 接口
* 在增加元素時,可以指定一個時間,只有到期后后才能從隊列中獲取元素。
*/
classDelayDemoimplementsDelayed{
longdelayTime;//過期時間
longtime;//多少秒過期
publicDelayDemo(inttime){
this.time=time;
// 如果time=3,System.currentTimeMillis()= 1616056291000 ,那么就是 1616056294000 時過期
this.delayTime=time*1000+System.currentTimeMillis();
}
/**
* 返回還有多久到期,DelayQueue隊列內部會不停的調用這個方法
* @param unit
* @return 返回值<=0 表示到期了
*/
@Override
publiclonggetDelay(TimeUnitunit){
returndelayTime-System.currentTimeMillis();
}
/**
* 排序使用,過期時間短的排到前面
* @param o
* @return
*/
@Override
publicintcompareTo(Delayedo){
return(int)(this.getDelay(TimeUnit.SECONDS)-o.getDelay(TimeUnit.SECONDS));
}
@Override
publicStringtoString(){
return"DelayDemo{"+
"delayTime="+delayTime+
", time="+time+
'}';
}
}
publicclassQueue05_DelayQueue{
publicstaticvoidmain(String[]args)throwsInterruptedException{
DelayQueue<DelayDemo> queue=newDelayQueue();
queue.add(newDelayDemo(3));
queue.add(newDelayDemo(2));
queue.add(newDelayDemo(5));
System.out.println(queue.take());
System.out.println(queue.take());
System.out.println(queue.take());
}
}
6. SynchronousQueue
Java復制代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* SynchronousQueue
* 長度為0的阻塞隊列,每一個put操作會阻塞,直到另一個take操作
* 線程池中 newCachedThreadPool 用到這個隊列
*
*/
publicclassQueue06_SynchronousQueue{
publicstaticvoidmain(String[]args)throwsInterruptedException{
SynchronousQueue<String> queue=newSynchronousQueue();
newThread(() -> {
try{
System.out.println("put 1 。。。。。。");
queue.put("1");
System.out.println("put 2 。。。。。。");
queue.put("2");
}catch(InterruptedExceptione){
e.printStackTrace();
}
}).start();
newThread(() -> {
try{
ThreadHelper.sleep(3000);
System.out.println("take "+queue.take()+" 。。。。。。");
ThreadHelper.sleep(3000);
System.out.println("take "+queue.take()+" 。。。。。。");
}catch(InterruptedExceptione){
e.printStackTrace();
}
}).start();
}
}
總結
- 上一篇: VUE2中provide 和 injec
- 下一篇: 设计的萌芽阶段_第一章 设计的萌芽阶段