前言
記得開始學(xué)習(xí)Java的時(shí)候撒汉,一遇到多線程情況就使用synchronized帜平,相對于當(dāng)時(shí)的我們來說synchronized是這么的神奇而又強(qiáng)大虑凛,那個(gè)時(shí)候我們賦予它一個(gè)名字“同步”敞嗡,也成為了我們解決多線程情況的百試不爽的良藥。但是榛瓮,隨著學(xué)習(xí)的進(jìn)行我們知道在JDK1.5之前synchronized是一個(gè)重量級鎖铺董,相對于j.u.c.Lock,它會顯得那么笨重禀晓,以至于我們認(rèn)為它不是那么的高效而慢慢摒棄它精续。
不過,隨著Javs SE 1.6對synchronized進(jìn)行的各種優(yōu)化后粹懒,synchronized并不會顯得那么重了重付。下面來一起探索synchronized的基本使用、實(shí)現(xiàn)機(jī)制凫乖、Java是如何對它進(jìn)行了優(yōu)化确垫、鎖優(yōu)化機(jī)制、鎖的存儲結(jié)構(gòu)等升級過程帽芽。
一删掀、synchronized作用
Synchronized是Java中解決并發(fā)問題的一種最常用的方法,也是最簡單的一種方法导街。Synchronized的作用主要有三個(gè):
原子性:確保線程互斥的訪問同步代碼披泪。
可見性:保證共享變量的修改能夠及時(shí)可見,其實(shí)是通過Java內(nèi)存模型中的 “對一個(gè)變量unlock操作之前搬瑰,必須要同步到主內(nèi)存中款票;如果對一個(gè)變量進(jìn)行l(wèi)ock操作,則將會清空工作內(nèi)存中此變量的值泽论,在執(zhí)行引擎使用此變量前艾少,需要重新從主內(nèi)存中l(wèi)oad操作或assign操作初始化變量值” 來保證的。
有序性:有效解決重排序問題佩厚,即 “一個(gè)unlock操作先行發(fā)生(happen-before)于后面對同一個(gè)鎖的lock操作”。
從語法上講说订,Synchronized可以把任何一個(gè)非null對象作為"鎖"抄瓦,在HotSpot JVM實(shí)現(xiàn)中潮瓶,鎖有個(gè)專門的名字:對象監(jiān)視器(Object Monitor)。
synchronized的三種應(yīng)用方式
- 1钙姊、修飾實(shí)例方法:當(dāng)synchronized作用在實(shí)例方法時(shí)毯辅,監(jiān)視器鎖(monitor)便是對象實(shí)例(this)。
- 2煞额、修飾靜態(tài)方法:當(dāng)synchronized作用在靜態(tài)方法時(shí)思恐,監(jiān)視器鎖(monitor)便是對象的Class實(shí)例,Java8Class實(shí)例存儲在堆中膊毁,因此靜態(tài)方法鎖相當(dāng)于該類的一個(gè)全局鎖胀莹。
- 3、修飾代碼塊:當(dāng)synchronized作用在某一個(gè)對象實(shí)例時(shí)婚温,監(jiān)視器鎖(monitor)便是括號括起來的對象實(shí)例描焰。
Synchronized是Java中解決并發(fā)問題的一種最常用的方法,也是最簡單的一種方法栅螟。
Synchronized的作用主要有三個(gè):
- 1荆秦、確保線程互斥的訪問同步代碼。
- 2力图、保證共享變量的修改能夠及時(shí)可見步绸。
- 3、有效解決重排序問題吃媒。
注意
synchronized 內(nèi)置鎖 是一種 對象鎖(鎖的是對象而非引用變量)瓤介,作用粒度是對象 ,可以用來實(shí)現(xiàn)對 臨界資源的同步互斥訪問 晓折,是 可重入 的惑朦。其可重入最大的作用是避免死鎖,如:子類同步方法調(diào)用了父類同步方法漓概,如沒有可重入的特性漾月,則會發(fā)生死鎖;
Java中每一個(gè)對象都可以作為鎖胃珍,這是synchronized實(shí)現(xiàn)同步的基礎(chǔ)梁肿。
二、同步原理
數(shù)據(jù)同步需要依賴鎖觅彰,那鎖的同步又依賴誰吩蔑?synchronized給出的答案是在軟件層面依賴JVM,而j.u.c.Lock給出的答案是在硬件層面依賴特殊的CPU指令填抬。
當(dāng)一個(gè)線程訪問同步代碼塊時(shí)烛芬,首先是需要得到鎖才能執(zhí)行同步代碼,當(dāng)退出或者拋出異常時(shí)必須要釋放鎖,那么它是如何來實(shí)現(xiàn)這個(gè)機(jī)制的呢赘娄?我們先看一段簡單的代碼:
static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}
對應(yīng)的字節(jié)碼為
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #2 // <- lock引用 (synchronized開始)
3: dup
4: astore_1 // lock引用 -> slot 1
5: monitorenter // 將 lock對象 MarkWord 置為 Monitor 指針
6: getstatic #3 // <- i
9: iconst_1 // 準(zhǔn)備常數(shù) 1
10: iadd // +1
11: putstatic #3 // -> i
14: aload_1 // <- lock引用
15: monitorexit // 將 lock對象 MarkWord 重置, 喚醒 EntryList
16: goto 24
19: astore_2 // e -> slot 2
20: aload_1 // <- lock引用
21: monitorexit // 將 lock對象 MarkWord 重置, 喚醒 EntryList
22: aload_2 // <- slot 2 (e)
23: athrow // throw e
24: return
Exception table:
from to target type
6 16 19 any
19 22 19 any
LineNumberTable:
line 8: 0
line 9: 6
line 10: 14
line 11: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
反編譯結(jié)果:
-
monitorenter:每個(gè)對象都是一個(gè)監(jiān)視器鎖(monitor)仆潮。當(dāng)monitor被占用時(shí)就會處于鎖定狀態(tài),線程執(zhí)行monitorenter指令時(shí)嘗試獲取monitor的所有權(quán)遣臼,過程如下:
- 1性置、如果monitor的進(jìn)入數(shù)為0,則該線程進(jìn)入monitor揍堰,然后將進(jìn)入數(shù)設(shè)置為1鹏浅,該線程即為monitor的所有者;
- 2屏歹、如果線程已經(jīng)占有該monitor隐砸,只是重新進(jìn)入,則進(jìn)入monitor的進(jìn)入數(shù)加1西采;
- 3凰萨、如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài)械馆,直到monitor的進(jìn)入數(shù)為0胖眷,再重新嘗試獲取monitor的所有權(quán);
-
monitorexit:執(zhí)行monitorexit的線程必須是objectref所對應(yīng)的monitor的所有者霹崎。指令執(zhí)行時(shí)珊搀,monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0尾菇,那線程退出monitor境析,不再是這個(gè)monitor的所有者。其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè) monitor 的所有權(quán)派诬。
- monitorexit指令出現(xiàn)了兩次劳淆,第1次為同步正常退出釋放鎖;第2次為發(fā)生異常退出釋放鎖默赂;
通過上面兩段描述沛鸵,我們應(yīng)該能很清楚的看出Synchronized的實(shí)現(xiàn)原理,Synchronized的語義底層是通過一個(gè)monitor的對象來完成缆八,其實(shí)wait/notify等方法也依賴于monitor對象曲掰,這就是為什么只有在同步的塊或者方法中才能調(diào)用wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因奈辰。
再來看一下同步方法:
public class SynchronizedMethod {
public synchronized void method() {
System.out.println("Hello World!");
}
}
查看反編譯后結(jié)果:
反編譯結(jié)果:
從編譯的結(jié)果來看栏妖,方法的同步并沒有通過指令 monitorenter 和 monitorexit 來完成(理論上其實(shí)也可以通過這兩條指令來實(shí)現(xiàn)),不過相對于普通方法奖恰,其常量池中多了 ACC_SYNCHRONIZED 標(biāo)示符吊趾。JVM就是根據(jù)該標(biāo)示符來實(shí)現(xiàn)方法的同步的:
當(dāng)方法調(diào)用時(shí)宛裕,調(diào)用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置,如果設(shè)置了论泛,執(zhí)行線程將先獲取monitor续滋,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor孵奶。在方法執(zhí)行期間,其他任何線程都無法再獲得同一個(gè)monitor對象蜡峰。
兩種同步方式本質(zhì)上沒有區(qū)別了袁,只是方法的同步是一種隱式的方式來實(shí)現(xiàn),無需通過字節(jié)碼來完成湿颅。兩個(gè)指令的執(zhí)行是JVM通過調(diào)用操作系統(tǒng)的互斥原語mutex來實(shí)現(xiàn)载绿,被阻塞的線程會被掛起、等待重新調(diào)度油航,會導(dǎo)致“用戶態(tài)和內(nèi)核態(tài)”兩個(gè)態(tài)之間來回切換崭庸,對性能有較大影響。
三谊囚、synchronize底層原理
Java 虛擬機(jī)中的同步(synchronization)基于進(jìn)入和退出Monitor對象實(shí)現(xiàn)怕享, 無論是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)镰踏,還是隱式同步都是如此函筋。
在 Java 語言中,同步用的最多的地方可能是被 synchronized 修飾的同步方法奠伪。同步方法并不是由 monitorenter 和 monitorexit 指令來實(shí)現(xiàn)同步的跌帐,而是由方法調(diào)用指令讀取運(yùn)行時(shí)常量池中方法表結(jié)構(gòu)的 ACC_SYNCHRONIZED 標(biāo)志來隱式實(shí)現(xiàn)的。
同步代碼塊:monitorenter指令插入到同步代碼塊的開始位置绊率,monitorexit指令插入到同步代碼塊的結(jié)束位置谨敛,JVM需要保證每一個(gè)monitorenter都有一個(gè)monitorexit與之相對應(yīng)。任何對象都有一個(gè)monitor與之相關(guān)聯(lián)滤否,當(dāng)且一個(gè)monitor被持有之后脸狸,他將處于鎖定狀態(tài)。線程執(zhí)行到monitorenter指令時(shí)顽聂,將會嘗試獲取對象所對應(yīng)的monitor所有權(quán)肥惭,即嘗試獲取對象的鎖;
3.1 Java對象頭
在JVM中紊搪,對象在內(nèi)存中的布局分為三塊區(qū)域:對象頭蜜葱、實(shí)例變量和填充數(shù)據(jù)。如下:
實(shí)例變量:存放類的屬性數(shù)據(jù)信息耀石,包括父類的屬性信息牵囤,如果是數(shù)組的實(shí)例部分還包括數(shù)組的長度爸黄,這部分內(nèi)存按4字節(jié)對齊。
填充數(shù)據(jù):由于虛擬機(jī)要求對象起始地址必須是8字節(jié)的整數(shù)倍揭鳞。填充數(shù)據(jù)不是必須存在的炕贵,僅僅是為了字節(jié)對齊,這點(diǎn)了解即可野崇。
對象頭:Hotspot虛擬機(jī)的對象頭主要包括兩部分?jǐn)?shù)據(jù):Mark Word(標(biāo)記字段)称开、Klass Pointer(類型指針)。其中Klass Point是是對象指向它的類元數(shù)據(jù)的指針乓梨,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對象是哪個(gè)類的實(shí)例鳖轰,Mark Word用于存儲對象自身的運(yùn)行時(shí)數(shù)據(jù),它是實(shí)現(xiàn)輕量級鎖和偏向鎖的關(guān)鍵扶镀。
Java對象頭的長度
- Mark Word:用于存儲對象自身的運(yùn)行時(shí)數(shù)據(jù)蕴侣,如哈希碼(HashCode)、GC分代年齡臭觉、鎖狀態(tài)標(biāo)志昆雀、線程持有的鎖、偏向線程 ID蝠筑、偏向時(shí)間戳等等狞膘。Java對象頭一般占有兩個(gè)機(jī)器碼(在32位虛擬機(jī)中,1個(gè)機(jī)器碼等于4字節(jié)什乙,也就是32bit)客冈,但是如果對象是數(shù)組類型,則需要三個(gè)機(jī)器碼稳强,因?yàn)镴VM虛擬機(jī)可以通過Java對象的元數(shù)據(jù)信息確定Java對象的大小场仲,但是無法從數(shù)組的元數(shù)據(jù)來確認(rèn)數(shù)組的大小,所以用一塊來記錄數(shù)組長度退疫。
Mark Word 結(jié)構(gòu)
初始是無鎖狀態(tài)渠缕,其中Mark Word在默認(rèn)情況下存儲著對象的HashCode、分代年齡褒繁、鎖標(biāo)記位等以下是32位JVM的Mark Word默認(rèn)存儲結(jié)構(gòu)亦鳞。
在運(yùn)行期間MarkWord里存儲的數(shù)據(jù)會隨著鎖狀態(tài)的變化而變化。
下面是簡化后的 Mark Word
hash: 保存對象的哈希碼
age: 保存對象的分代年齡
biased_lock: 偏向鎖標(biāo)識位
lock: 鎖狀態(tài)標(biāo)識位
JavaThread*: 保存持有偏向鎖的線程ID
epoch: 保存偏向時(shí)間戳
3.2 對象頭中Mark Word與線程中Lock Record
在線程進(jìn)入同步代碼塊的時(shí)候棒坏,如果此同步對象沒有被鎖定燕差,即它的鎖標(biāo)志位是01,則虛擬機(jī)首先在當(dāng)前線程的棧中創(chuàng)建我們稱之為“鎖記錄(Lock Record)”的空間坝冕,用于存儲鎖對象的Mark Word的拷貝徒探,官方把這個(gè)拷貝稱為Displaced Mark Word。整個(gè)Mark Word及其拷貝至關(guān)重要喂窟。
Lock Record是線程私有的數(shù)據(jù)結(jié)構(gòu)测暗,每一個(gè)線程都有一個(gè)可用Lock Record列表央串,同時(shí)還有一個(gè)全局的可用列表。每一個(gè)被鎖住的對象Mark Word都會和一個(gè)Lock Record關(guān)聯(lián)(對象頭的MarkWord中的Lock Word指向Lock Record的起始地址)碗啄,同時(shí)Lock Record中有一個(gè)Owner字段存放擁有該鎖的線程的唯一標(biāo)識(或者object mark word)质和,表示該鎖被這個(gè)線程占用。如下圖所示為Lock Record的內(nèi)部結(jié)構(gòu):
Lock Record描述
Owner:初始時(shí)為NULL表示當(dāng)前沒有任何線程擁有該monitor record稚字,當(dāng)線程成功擁有該鎖后保存線程唯一標(biāo)識饲宿,當(dāng)鎖被釋放時(shí)又設(shè)置為NULL。
EntryQ:關(guān)聯(lián)一個(gè)系統(tǒng)互斥鎖(semaphore)胆描,阻塞所有試圖鎖住monitor record失敗的線程褒傅。
RcThis:表示blocked或waiting在該monitor record上的所有線程的個(gè)數(shù)。
Nest:用來實(shí)現(xiàn) 重入鎖的計(jì)數(shù)袄友。
HashCode:保存從對象頭拷貝過來的HashCode值(可能還包含GC age)。
Candidate:用來避免不必要的阻塞或等待線程喚醒霹菊,因?yàn)槊恳淮沃挥幸粋€(gè)線程能夠成功擁有鎖剧蚣,如果每次前一個(gè)釋放鎖的線程喚醒所有正在阻塞或等待的線程,會引起不必要的上下文切換(從阻塞到就緒然后因?yàn)楦偁庢i失敗又被阻塞)從而導(dǎo)致性能嚴(yán)重下降旋廷。Candidate只有兩種可能的值:0:表示沒有需要喚醒的線程鸠按,1:表示要喚醒一個(gè)繼任線程來競爭鎖。
3.3 監(jiān)視器(Monitor)
任何一個(gè)對象都有一個(gè)Monitor與之關(guān)聯(lián)饶碘,當(dāng)且一個(gè)Monitor被持有后目尖,它將處于鎖定狀態(tài)。Synchronized在JVM里的實(shí)現(xiàn)都是 基于進(jìn)入和退出Monitor對象來實(shí)現(xiàn)方法同步和代碼塊同步扎运,雖然具體實(shí)現(xiàn)細(xì)節(jié)不一樣瑟曲,但是都可以通過成對的MonitorEnter和MonitorExit指令來實(shí)現(xiàn)。
Monior:被翻譯為監(jiān)視器或管程豪治,我們可以把它理解為一個(gè)同步工具洞拨,也可以描述為一種同步機(jī)制,它通常被描述為一個(gè)對象负拟。與一切皆對象一樣烦衣,所有的Java對象是天生的Monitor,每一個(gè)Java對象都有成為Monitor的潛質(zhì)掩浙,因?yàn)樵贘ava的設(shè)計(jì)中 花吟,每一個(gè)Java對象自打娘胎里出來就帶了一把看不見的鎖,它叫做內(nèi)部鎖或者M(jìn)onitor鎖厨姚。Monitor 是線程私有的數(shù)據(jù)結(jié)構(gòu)衅澈,每一個(gè)線程都有一個(gè)可用monitor record列表,同時(shí)還有一個(gè)全局的可用列表谬墙。每一個(gè)被鎖住的對象都會和一個(gè)monitor關(guān)聯(lián)(對象頭的MarkWord中的LockWord指向monitor的起始地址)矾麻,同時(shí)monitor中有一個(gè)Owner字段存放擁有該鎖的線程的唯一標(biāo)識纱耻,表示該鎖被這個(gè)線程占用。其結(jié)構(gòu)如下:
如果使用 synchronized 給對象上鎖(重量級)之后险耀,該對象頭的 Mark Word 中就被設(shè)置指向 Monitor 對象的指針弄喘,Monitor 結(jié)構(gòu)如下:
synchronized關(guān)鍵字用來修飾的位置不同,其實(shí)現(xiàn)原理也是不同的甩牺。鎖住的對象也是不同的蘑志。在Java中,每個(gè)對象里面隱式的存在一個(gè)叫monitor(對象監(jiān)視器)的對象贬派,這個(gè)對象源碼是采用C++實(shí)現(xiàn)的急但。
通常說Synchronized的對象鎖,MarkWord鎖標(biāo)識位為10搞乏,其中指針指向的是Monitor對象的起始地址波桩。在Java虛擬機(jī)(HotSpot)中,Monitor是由ObjectMonitor實(shí)現(xiàn)的请敦,其主要數(shù)據(jù)結(jié)構(gòu)如下(位于HotSpot虛擬機(jī)源碼ObjectMonitor.hpp文件镐躲,C++實(shí)現(xiàn)的):
ObjectMonitor() {
_header = NULL; //是一個(gè)markOop類型,markOop就是對象頭中的Mark Word
_count = 0; //搶占該鎖的線程數(shù) 約等于 WaitSet.size + EntryList.size
_waiters = 0, //等待線程數(shù)
_recursions = 0; //鎖重入次數(shù)
_object = NULL; //監(jiān)視器鎖寄生的對象侍筛。鎖不是平白出現(xiàn)的萤皂,而是寄托存儲于對象中
_owner = NULL; //指向獲得ObjectMonitor對象的線程或基礎(chǔ)鎖
_WaitSet = NULL; //處于wait狀態(tài)的線程,會被加入到_WaitSet
_WaitSetLock = 0 ; //保護(hù)WaitSet的一個(gè)自旋鎖(monitor大鎖里面的一個(gè)小鎖匣椰,這個(gè)小鎖用來保護(hù)_WaitSet更改)
_Responsible = NULL ;
_succ = NULL ; //當(dāng)鎖被前一個(gè)線程釋放裆熙,會指定一個(gè)假定繼承者線程,但是它不一定最終獲得鎖禽笑。
_cxq = NULL ; //ContentionList
FreeNext = NULL ;
_EntryList = NULL ; //處于等待鎖block狀態(tài)的線程入录,即未獲取鎖被阻塞或者被wait的線程重新進(jìn)入被放入entryList中
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ; //當(dāng)前owner是thread還是BasicLock
_previous_owner_tid = 0;//當(dāng)前owner的線程id
}
下面解釋objectMonitor中屬性的含義
屬性 | 含義 |
---|---|
_header | 定義:volatile markOop _header; 說明:_header是一個(gè)markOop類型,markOop就是對象頭中的Mark Word |
_count | 定義:volatile intptr_t _count; 說明:搶占該鎖的線程數(shù) 約等于 WaitSet.size + EntryList.size |
_waiters | 定義:volatile intptr_t _waiters; 說明:等待線程數(shù) |
_recursions | 定義:volatile intptr_t _recursions; 說明:鎖重入次數(shù) |
_object | 定義:void* volatile _object; 說明:監(jiān)視器鎖寄生的對象佳镜。鎖不是平白出現(xiàn)的纷跛,而是寄托存儲于對象中 |
_owner | 定義:void * volatile _owner; 說明:指向獲得ObjectMonitor對象的線程或基礎(chǔ)鎖 |
_WaitSet | 定義:ObjectWaiter * volatile _WaitSet; 說明:處于wait狀態(tài)的線程,被加入到這個(gè)linkedList |
_WaitSetLock | 定義:volatile int _WaitSetLock; 說明:protects Wait Queue - simple spinlock 邀杏,保護(hù)WaitSet的一個(gè)自旋鎖(monitor大鎖里面的一個(gè)小鎖贫奠,這個(gè)小鎖用來保護(hù)_WaitSet更改) |
_Responsible | 定義:Thread * volatile _Responsible 說明:未知 參考:http://www.reibang.com/p/09de11d71ef8 |
_succ | 定義:Thread * volatile _succ ; 說明:當(dāng)鎖被前一個(gè)線程釋放,會指定一個(gè)假定繼承者線程望蜡,但是它不一定最終獲得鎖唤崭。 參考:http://www.reibang.com/p/09de11d71ef8 |
_cxq | 定義:ObjectWaiter * volatile _cxq ; 說明:ContentionList 參考:http://www.reibang.com/p/09de11d71ef8 |
FreeNext | 定義:ObjectMonitor * FreeNext ; 說明:未知 |
_EntryList | 定義:ObjectWaiter * volatile _EntryList ; 說明:未獲取鎖被阻塞或者被wait的線程重新進(jìn)入被放入entryList中 |
_SpinFreq | 定義:volatile int _SpinFreq ; 說明:未知 可能是獲取鎖的成功率 |
_SpinClock | 定義:volatile int _SpinClock ; 說明:未知 |
OwnerIsThread | 定義:int OwnerIsThread ; 說明:當(dāng)前owner是thread還是BasicLock |
_previous_owner_tid | 定義:volatile jlong _previous_owner_tid; 說明:當(dāng)前owner的線程id |
ObjectMonitor中有兩個(gè)隊(duì)列,_WaitSet 和 _EntryList脖律,用來保存ObjectWaiter對象列表( 每個(gè)等待鎖的線程都會被封裝成ObjectWaiter對象 )谢肾,_owner指向持有ObjectMonitor對象的線程,當(dāng)多個(gè)線程同時(shí)訪問一段同步代碼時(shí):
* 1小泉、首先會進(jìn)入 _EntryList 集合芦疏,當(dāng)線程獲取到對象的monitor后冕杠,進(jìn)入 _Owner區(qū)域并把monitor中的owner變量設(shè)置為當(dāng)前線程,同時(shí)monitor中的計(jì)數(shù)器count加1酸茴。
* 2分预、若線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的monitor薪捍,owner變量恢復(fù)為null笼痹,count自減1,同時(shí)該線程進(jìn)入 WaitSet集合中等待被喚醒酪穿。
* 3凳干、若當(dāng)前線程執(zhí)行完畢,也將釋放monitor(鎖)并復(fù)位count的值被济,以便其他線程進(jìn)入獲取monitor(鎖)救赐。
同時(shí),Monitor對象存在于每個(gè)Java對象的對象頭Mark Word中(存儲的指針的指向)只磷,Synchronized鎖便是通過這種方式獲取鎖的经磅,也是為什么Java中任意對象可以作為鎖的原因,同時(shí)notify/notifyAll/wait等方法會使用到Monitor鎖對象喳瓣,所以必須在同步代碼塊中使用。
監(jiān)視器Monitor有兩種同步方式:互斥與協(xié)作赞别。多線程環(huán)境下線程之間如果需要共享數(shù)據(jù)畏陕,需要解決互斥訪問數(shù)據(jù)的問題,監(jiān)視器可以確保監(jiān)視器上的數(shù)據(jù)在同一時(shí)刻只會有一個(gè)線程在訪問仿滔。
什么時(shí)候需要協(xié)作惠毁? 比如:
一個(gè)線程向緩沖區(qū)寫數(shù)據(jù),另一個(gè)線程從緩沖區(qū)讀數(shù)據(jù)崎页,如果讀線程發(fā)現(xiàn)緩沖區(qū)為空就會等待鞠绰,當(dāng)寫線程向緩沖區(qū)寫入數(shù)據(jù),就會喚醒讀線程飒焦,這里讀線程和寫線程就是一個(gè)合作關(guān)系蜈膨。JVM通過Object類的wait方法來使自己等待,在調(diào)用wait方法后牺荠,該線程會釋放它持有的監(jiān)視器翁巍,直到其他線程通知它才有執(zhí)行的機(jī)會。一個(gè)線程調(diào)用notify方法通知在等待的線程休雌,這個(gè)等待的線程并不會馬上執(zhí)行灶壶,而是要通知線程釋放監(jiān)視器后,它重新獲取監(jiān)視器才有執(zhí)行的機(jī)會杈曲。如果剛好喚醒的這個(gè)線程需要的監(jiān)視器被其他線程搶占,那么這個(gè)線程會繼續(xù)等待。Object類中的notifyAll方法可以解決這個(gè)問題龄广,它可以喚醒所有等待的線程洛口,總有一個(gè)線程執(zhí)行。
對象關(guān)聯(lián)的 ObjectMonitor 對象有一個(gè)線程內(nèi)部競爭鎖的機(jī)制语婴,如下圖所示:
四、鎖的優(yōu)化
從JDK5引入了現(xiàn)代操作系統(tǒng)新增加的CAS原子操作( JDK5中并沒有對synchronized關(guān)鍵字做優(yōu)化,而是體現(xiàn)在J.U.C中羔挡,所以在該版本concurrent包有更好的性能 ),從JDK6開始间唉,就對synchronized的實(shí)現(xiàn)機(jī)制進(jìn)行了較大調(diào)整绞灼,包括使用JDK5引進(jìn)的CAS自旋之外,還增加了自適應(yīng)的CAS自旋呈野、鎖消除低矮、鎖粗化、偏向鎖被冒、輕量級鎖這些優(yōu)化策略军掂。由于此關(guān)鍵字的優(yōu)化使得性能極大提高,同時(shí)語義清晰昨悼、操作簡單蝗锥、無需手動(dòng)關(guān)閉,所以推薦在允許的情況下盡量使用此關(guān)鍵字率触,同時(shí)在性能上此關(guān)鍵字還有優(yōu)化的空間终议。
鎖主要存在四種狀態(tài),依次是:無鎖狀態(tài)葱蝗、偏向鎖狀態(tài)穴张、輕量級鎖狀態(tài)、重量級鎖狀態(tài)两曼,鎖可以從偏向鎖升級到輕量級鎖皂甘,再升級的重量級鎖。但是鎖的升級是單向的悼凑,也就是說只能從低到高升級偿枕,不會出現(xiàn)鎖的降級。
在 JDK 1.6 中默認(rèn)是開啟偏向鎖和輕量級鎖的户辫,可以通過-XX:-UseBiasedLocking來禁用偏向鎖益老。
4.1 偏向鎖
偏向鎖是Java 6之后加入的新鎖,它是一種針對加鎖操作的優(yōu)化手段寸莫,經(jīng)過研究發(fā)現(xiàn)捺萌,在大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得桃纯,因此為了減少同一線程獲取鎖(會涉及到一些CAS操作酷誓,耗時(shí))的代價(jià)而引入偏向鎖。
偏向鎖的核心思想是态坦,如果一個(gè)線程獲得了鎖盐数,那么鎖就進(jìn)入偏向模式,此時(shí)Mark Word 的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu)伞梯,當(dāng)這個(gè)線程再次請求鎖時(shí)玫氢,無需再做任何同步操作,即獲取鎖的過程谜诫,這樣就省去了大量有關(guān)鎖申請的操作漾峡,從而也就提供程序的性能。所以喻旷,對于沒有鎖競爭的場合生逸,偏向鎖有很好的優(yōu)化效果,畢竟極有可能連續(xù)多次是同一個(gè)線程申請相同的鎖且预。但是對于鎖競爭比較激烈的場合槽袄,偏向鎖就失效了,因?yàn)檫@樣場合極有可能每次申請鎖的線程都是不相同的锋谐,因此這種場合下不應(yīng)該使用偏向鎖遍尺,否則會得不償失,需要注意的是涮拗,偏向鎖失敗后乾戏,并不會立即膨脹為重量級鎖,而是先升級為輕量級鎖多搀。
偏向鎖
作用:減少同一線程獲取鎖的代價(jià)
引入偏向鎖是因?yàn)榇蠖鄶?shù)情況下歧蕉,鎖并不存在多線程競爭灾部,總是由同一線程多次獲得康铭。
獲取鎖
- 1、檢測Mark Word是否為可偏向狀態(tài)赌髓,即是否為偏向鎖1从藤,鎖標(biāo)識位為01。
- 2锁蠕、若為可偏向狀態(tài)夷野,則測試線程ID是否為當(dāng)前線程ID,如果是荣倾,則執(zhí)行步驟(5)悯搔,否則執(zhí)行步驟(3)。
- 3舌仍、如果線程ID不為當(dāng)前線程ID妒貌,則通過CAS操作競爭鎖通危,競爭成功,則將Mark Word的線程ID替換為當(dāng)前線程ID灌曙,否則執(zhí)行線程(4)菊碟。
- 4、通過CAS競爭鎖失敗在刺,證明當(dāng)前存在多線程競爭情況逆害,當(dāng)?shù)竭_(dá)全局安全點(diǎn),獲得偏向鎖的線程被掛起蚣驼,偏向鎖升級為輕量級鎖魄幕,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼塊。
- 5隙姿、執(zhí)行同步代碼塊梅垄。
釋放鎖
偏向鎖的釋放采用了一種只有競爭才會釋放鎖的機(jī)制,線程是不會主動(dòng)去釋放偏向鎖输玷,需要等待其他線程來競爭队丝。偏向鎖的撤銷需要等待全局安全點(diǎn)(這個(gè)時(shí)間點(diǎn)是上沒有正在執(zhí)行的代碼)。其步驟如下:
- 1欲鹏、暫停擁有偏向鎖的線程机久,判斷鎖對象是否還處于被鎖定狀態(tài);
- 2赔嚎、撤銷偏向鎖膘盖,恢復(fù)到無鎖狀態(tài)(01)或者輕量級鎖的狀態(tài);
注意:
輕量級鎖在沒有競爭時(shí)(就自己這個(gè)線程)尤误,每次重入仍然需要執(zhí)行 CAS 操作侠畔。
Java 6 中引入了偏向鎖來做進(jìn)一步優(yōu)化:只有第一次使用 CAS 將線程 ID 設(shè)置到對象的 Mark Word 頭,之后發(fā)現(xiàn)這個(gè)線程 ID 是自己的就表示沒有競爭损晤,不用重新 CAS软棺。以后只要不發(fā)生競爭,這個(gè)對象就歸該線程所有尤勋。
static final Object obj = new Object();
public static void m1() {
synchronized( obj ) {
// 同步塊 A
m2();
}
}
public static void m2() {
synchronized( obj ) {
// 同步塊 B
m3();
}
}
public static void m3() {
synchronized( obj ) {
// 同步塊 C
}
}
偏向狀態(tài)
一個(gè)對象創(chuàng)建時(shí):
- 如果開啟了偏向鎖(默認(rèn)開啟)喘落,那么對象創(chuàng)建后,markword 值為 0x05 即最后 3 位為 101最冰,這時(shí)它的thread瘦棋、epoch、age 都為 0暖哨。
- 偏向鎖是默認(rèn)是延遲的赌朋,不會在程序啟動(dòng)時(shí)立即生效,如果想避免延遲,可以加 VM 參數(shù) -XX:BiasedLockingStartupDelay=0 來禁用延遲沛慢。
- 如果沒有開啟偏向鎖服球,那么對象創(chuàng)建后,markword 值為 0x01 即最后 3 位為 001颠焦,這時(shí)它的 hashcode斩熊、age 都為 0,第一次用到 hashcode 時(shí)才會賦值伐庭。
測試延遲特性
- 1粉渠、測試偏向鎖
利用 jol 第三方工具來查看對象頭信息
// 添加虛擬機(jī)參數(shù) -XX:BiasedLockingStartupDelay=0
public static void main(String[] args) throws IOException {
Dog d = new Dog();
ClassLayout classLayout = ClassLayout.parseInstance(d);
new Thread(() -> {
log.debug("synchronized 前");
System.out.println(classLayout.toPrintableSimple(true));
synchronized (d) {
log.debug("synchronized 中");
System.out.println(classLayout.toPrintableSimple(true));
}
log.debug("synchronized 后");
System.out.println(classLayout.toPrintableSimple(true));
}, "t1").start();
}
輸出
11:08:58.117 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
11:08:58.121 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101
11:08:58.121 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101
注意:處于偏向鎖的對象解鎖后,線程 id 仍存儲于對象頭中圾另。
- 2霸株、測試禁用
在上面測試代碼運(yùn)行時(shí)在添加 VM 參數(shù) -XX:-UseBiasedLocking 禁用偏向鎖
輸出:
11:13:10.018 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
11:13:10.021 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000
11:13:10.021 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
- 3、測試 hashCode
正常狀態(tài)對象一開始是沒有 hashCode 的集乔,第一次調(diào)用才生成
撤銷 - 調(diào)用對象 hashCode
調(diào)用了對象的 hashCode去件,但偏向鎖的對象 MarkWord 中存儲的是線程 id,如果調(diào)用 hashCode 會導(dǎo)致偏向鎖被撤銷扰路。
- 輕量級鎖會在鎖記錄中記錄 hashCode
- 重量級鎖會在 Monitor 中記錄 hashCode
在調(diào)用 hashCode 后使用偏向鎖尤溜,記得去掉 -XX:-UseBiasedLocking
輸出:
11:22:10.386 c.TestBiased [main] - 調(diào)用 hashCode:1778535015
11:22:10.391 c.TestBiased [t1] - synchronized 前
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
11:22:10.393 c.TestBiased [t1] - synchronized 中
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000
11:22:10.393 c.TestBiased [t1] - synchronized 后
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
撤銷 - 其它線程使用對象
當(dāng)有其它線程使用偏向鎖對象時(shí),會將偏向鎖升級為輕量級鎖
private static void test2() throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
synchronized (TestBiased.class) {
TestBiased.class.notify();
}
// 如果不用 wait/notify 使用 join 必須打開下面的注釋
// 因?yàn)椋簍1 線程不能結(jié)束汗唱,否則底層線程可能被 jvm 重用作為 t2 線程宫莱,底層線程 id 是一樣的
/*try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}*/
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (TestBiased.class) {
try {
TestBiased.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}, "t2");
t2.start();
}
輸出:
[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
撤銷 - 調(diào)用 wait/notify
public static void main(String[] args) throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
try {
d.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t1");
t1.start();
new Thread(() -> {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
log.debug("notify");
d.notify();
}
}, "t2").start();
}
輸出:
[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101
[t2] - notify
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010
批量重偏向
如果對象雖然被多個(gè)線程訪問,但沒有競爭哩罪,這時(shí)偏向了線程 T1 的對象仍有機(jī)會重新偏向 T2授霸,重偏向會重置對象的 Thread ID。
當(dāng)撤銷偏向鎖閾值超過 20 次后际插,jvm 會這樣覺得碘耳,我是不是偏向錯(cuò)了呢,于是會在給這些對象加鎖時(shí)重新偏向至加鎖線程框弛。
private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t2");
t2.start();
}
輸出:
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - ===============>
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
4.2 輕量級鎖
倘若偏向鎖失敗辛辨,虛擬機(jī)并不會立即升級為重量級鎖,它還會嘗試使用一種稱為輕量級鎖的優(yōu)化手段(1.6之后加入的)功咒,此時(shí)Mark Word 的結(jié)構(gòu)也變?yōu)檩p量級鎖的結(jié)構(gòu)愉阎。輕量級鎖能夠提升程序性能的依據(jù)是“對絕大部分的鎖绞蹦,在整個(gè)同步周期內(nèi)都不存在競爭”力奋,注意這是經(jīng)驗(yàn)數(shù)據(jù)。需要了解的是幽七,輕量級鎖所適應(yīng)的場景是線程交替執(zhí)行同步塊的場合景殷,如果存在同一時(shí)間訪問同一鎖的場合,就會導(dǎo)致輕量級鎖膨脹為重量級鎖。
獲取鎖:
- 1猿挚、判斷當(dāng)前對象是否處于無鎖狀態(tài)(hashcode咐旧、0、01)绩蜻,若是铣墨,則JVM首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲鎖對象目前的Mark Word的拷貝(官方把這份拷貝加了一個(gè)Displaced前綴办绝,即Displaced Mark Word)伊约;否則執(zhí)行步驟(3);
- 2孕蝉、JVM利用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針屡律,如果成功表示競爭到鎖,則將鎖標(biāo)志位變成00(表示此對象處于輕量級鎖狀態(tài))降淮,執(zhí)行同步操作超埋;如果失敗則執(zhí)行步驟(3);
- 3佳鳖、判斷當(dāng)前對象的Mark Word是否指向當(dāng)前線程的棧幀霍殴,如果是則表示當(dāng)前線程已經(jīng)持有當(dāng)前對象的鎖,則直接執(zhí)行同步代碼塊系吩;否則只能說明該鎖對象已經(jīng)被其他線程搶占了繁成,這時(shí)輕量級鎖需要膨脹為重量級鎖,鎖標(biāo)志位變成10淑玫,后面等待的線程將會進(jìn)入阻塞狀態(tài)巾腕;
釋放鎖:
輕量級鎖的釋放也是通過CAS操作來進(jìn)行的,主要步驟如下:
- 1絮蒿、取出在獲取輕量級鎖保存在Displaced Mark Word中的數(shù)據(jù)尊搬;
- 2、用CAS操作將取出的數(shù)據(jù)替換當(dāng)前對象的Mark Word中土涝,如果成功佛寿,則說明釋放鎖成功,否則執(zhí)行(3)但壮;
- 3冀泻、如果CAS操作替換失敗,說明有其他線程嘗試獲取該鎖蜡饵,則需要在釋放鎖的同時(shí)需要喚醒被掛起的線程弹渔。
對于輕量級鎖,其性能提升的依據(jù)是“對于絕大部分的鎖溯祸,在整個(gè)生命周期內(nèi)都是不會存在競爭的”肢专,如果打破這個(gè)依據(jù)則除了互斥的開銷外舞肆,還有額外的CAS操作,因此在有多線程競爭的情況下博杖,輕量級鎖比重量級鎖更慢椿胯;
輕量級鎖的使用場景
- 1、如果一個(gè)對象雖然有多線程要加鎖剃根,但加鎖的時(shí)間是錯(cuò)開的(也就是沒有競爭)哩盲,那么可以使用輕量級鎖來優(yōu)化。
- 2狈醉、輕量級鎖對使用者是透明的种冬,即語法仍然是 synchronized。
假設(shè)有兩個(gè)方法同步塊舔糖,利用同一個(gè)對象加鎖
static final Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步塊 A
method2();
}
}
public static void method2() {
synchronized( obj ) {
// 同步塊 B
}
}
-
1娱两、創(chuàng)建鎖記錄(Lock Record)對象,每個(gè)線程的棧幀都會包含一個(gè)鎖記錄的結(jié)構(gòu)金吗,內(nèi)部可以存儲鎖定對象的Mark Word十兢。
-
2、讓鎖記錄中 Object reference 指向鎖對象摇庙,并嘗試用 cas 替換 Object 的 Mark Word旱物,將 Mark Word 的值存入鎖記錄。
-
3卫袒、如果 cas 替換成功宵呛,對象頭中存儲了 鎖記錄地址和狀態(tài) 00 ,表示由該線程給對象加鎖夕凝,這時(shí)圖示如下
-
4宝穗、如果 cas 失敗,有兩種情況:
- 如果是其它線程已經(jīng)持有了該 Object 的輕量級鎖码秉,這時(shí)表明有競爭逮矛,進(jìn)入鎖膨脹過程。
-
如果是自己執(zhí)行了 synchronized 鎖重入转砖,那么再添加一條 Lock Record 作為重入的計(jì)數(shù)须鼎。
-
5、當(dāng)退出 synchronized 代碼塊(解鎖時(shí))如果有取值為 null 的鎖記錄府蔗,表示有重入晋控,這時(shí)重置鎖記錄,表示重入計(jì)數(shù)減一姓赤。
-
6赡译、當(dāng)退出 synchronized 代碼塊(解鎖時(shí))鎖記錄的值不為 null,這時(shí)使用 cas 將 Mark Word 的值恢復(fù)給對象頭模捂。
- 成功捶朵,則解鎖成功。
- 失敗狂男,說明輕量級鎖進(jìn)行了鎖膨脹或已經(jīng)升級為重量級鎖综看,進(jìn)入重量級鎖解鎖流程。
4.3 鎖膨脹
如果在嘗試加輕量級鎖的過程中岖食,CAS 操作無法成功红碑,這時(shí)一種情況就是有其它線程為此對象加上了輕量級鎖(有競爭),這時(shí)需要進(jìn)行鎖膨脹泡垃,將輕量級鎖變?yōu)橹亓考夋i析珊。
static Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步塊
}
}
-
1、當(dāng) Thread-1 進(jìn)行輕量級加鎖時(shí)蔑穴,Thread-0 已經(jīng)對該對象加了輕量級鎖
-
2忠寻、這時(shí) Thread-1 加輕量級鎖失敗,進(jìn)入鎖膨脹流程
- 即為 Object 對象申請 Monitor 鎖存和,讓 Object 指向重量級鎖地址奕剃。
- 然后自己進(jìn)入 Monitor 的 EntryList BLOCKED。
- 3捐腿、當(dāng) Thread-0 退出同步塊解鎖時(shí)纵朋,使用 cas 將 Mark Word 的值恢復(fù)給對象頭,失敗茄袖。這時(shí)會進(jìn)入重量級解鎖流程操软,即按照 Monitor 地址找到 Monitor 對象,設(shè)置 Owner 為 null宪祥,喚醒 EntryList 中 BLOCKED 線程聂薪。
4.4 自旋優(yōu)化
重量級鎖競爭的時(shí)候,還可以使用自旋來進(jìn)行優(yōu)化蝗羊,如果當(dāng)前線程自旋成功(即這時(shí)候持鎖線程已經(jīng)退出了同步塊胆建,釋放了鎖),這時(shí)當(dāng)前線程就可以避免阻塞肘交。
引入自旋鎖的原因:互斥同步對性能最大的影響是阻塞的實(shí)現(xiàn)笆载,因?yàn)閽炱鹁€程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性能帶來很大的壓力涯呻。同時(shí)虛擬機(jī)的開發(fā)團(tuán)隊(duì)也注意到在許多應(yīng)用上面凉驻,共享數(shù)據(jù)的鎖定狀態(tài)只會持續(xù)很短一段時(shí)間,為了這一段很短的時(shí)間頻繁地阻塞和喚醒線程是非常不值得的复罐。
自旋鎖:讓該線程執(zhí)行一段無意義的忙循環(huán)(自旋)等待一段時(shí)間涝登,不會被立即掛起(自旋不放棄處理器的執(zhí)行時(shí)間),看持有鎖的線程是否會很快釋放鎖效诅。自旋鎖在JDK 1.4.2中引入胀滚,默認(rèn)關(guān)閉趟济,但是可以使用-XX:+UseSpinning開開啟;在JDK1.6中默認(rèn)開啟咽笼。
自旋鎖的缺點(diǎn):自旋等待不能替代阻塞顷编,雖然它可以避免線程切換帶來的開銷,但是它占用了處理器的時(shí)間剑刑。如果持有鎖的線程很快就釋放了鎖媳纬,那么自旋的效率就非常好;反之施掏,自旋的線程就會白白消耗掉處理器的資源钮惠,它不會做任何有意義的工作,這樣反而會帶來性能上的浪費(fèi)七芭。所以說素挽,自旋等待的時(shí)間(自旋的次數(shù))必須要有一個(gè)限度,例如讓其循環(huán)10次狸驳,如果自旋超過了定義的時(shí)間仍然沒有獲取到鎖毁菱,則應(yīng)該被掛起(進(jìn)入阻塞狀態(tài))。通過參數(shù)-XX:PreBlockSpin可以調(diào)整自旋次數(shù)锌历,默認(rèn)的自旋次數(shù)為10贮庞。
自適應(yīng)的自旋鎖:JDK1.6引入自適應(yīng)的自旋鎖,自適應(yīng)就意味著自旋的次數(shù)不再是固定的究西,它是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定:如果在同一個(gè)鎖的對象上窗慎,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運(yùn)行中卤材,那么虛擬機(jī)就會認(rèn)為這次自旋也很有可能再次成功遮斥,進(jìn)而它將允許自旋等待持續(xù)相對更長的時(shí)間。如果對于某個(gè)鎖扇丛,自旋很少成功獲得過术吗,那在以后要獲取這個(gè)鎖時(shí)將可能省略掉自旋過程,以避免浪費(fèi)處理器資源帆精。簡單來說较屿,就是線程如果自旋成功了,則下次自旋的次數(shù)會更多卓练,如果自旋失敗了隘蝎,則自旋的次數(shù)就會減少。
自旋鎖使用場景:從輕量級鎖獲取的流程中我們知道襟企,當(dāng)線程在獲取輕量級鎖的過程中執(zhí)行CAS操作失敗時(shí)嘱么,是要通過自旋來獲取重量級鎖的。(見前面“輕量級鎖”)
自旋重試成功的情況
自旋重試失敗的情況
注意:
- 1顽悼、自旋會占用 CPU 時(shí)間曼振,單核 CPU 自旋就是浪費(fèi)几迄,多核 CPU 自旋才能發(fā)揮優(yōu)勢。
- 2冰评、在 Java 6 之后自旋鎖是自適應(yīng)的映胁,比如對象剛剛的一次自旋操作成功過,那么認(rèn)為這次自旋成功的可能性會高集索,就多自旋幾次屿愚;反之汇跨,就少自旋甚至不自旋务荆,總之,比較智能穷遂。
- 3函匕、Java 7 之后不能控制是否開啟自旋功能。
4.5 重量級鎖
重量級鎖通過對象內(nèi)部的監(jiān)視器(monitor)實(shí)現(xiàn)蚪黑,其中monitor的本質(zhì)是依賴于底層操作系統(tǒng)的Mutex Lock實(shí)現(xiàn)盅惜,操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)到內(nèi)核態(tài)的切換,切換成本非常高忌穿。
當(dāng)鎖升級為輕量級鎖之后抒寂,如果依然有新線程過來競爭鎖,首先新線程會自旋嘗試獲取鎖掠剑,嘗試到一定次數(shù)(默認(rèn)10次)屈芜。
對象關(guān)聯(lián)的 ObjectMonitor 對象有一個(gè)線程內(nèi)部競爭鎖的機(jī)制,如下圖所示:
synchronized線程內(nèi)部競爭鎖結(jié)構(gòu):
Contention List:競爭隊(duì)列朴译,所有請求鎖的線程首先被放在這個(gè)競爭隊(duì)列中井佑;
Entry List:Contention List中那些有資格成為候選資源的線程被移動(dòng)到Entry List中;
Wait Set:哪些調(diào)用wait方法被阻塞的線程被放置在這里眠寿;
OnDeck:任意時(shí)刻躬翁,最多只有一個(gè)線程正在競爭鎖資源,該線程被成為OnDeck盯拱;
Owner:當(dāng)前已經(jīng)獲取到鎖資源的線程被稱為Owner盒发;
大量并發(fā)線程會在Contention List中,然后將有資格成為候選的放到Entry List中狡逢。調(diào)用了wait的線程放到Wait Set中迹辐,當(dāng)被喚醒后會放到Entry List中。
指定Entry List中的某個(gè)線程為OnDeck線程(一般是最先進(jìn)去的那個(gè)線程)甚侣,然后OnDeck線程去競爭鎖明吩,但是此時(shí)其他未進(jìn)入Contention List的線程會先自旋一下看是否能獲得到鎖,所以說synchronied不是公平的殷费。
JVM 每次從Waiting Queue 的尾部取出一個(gè)線程放到OnDeck作為候選者印荔,但是如果并發(fā)比較高低葫,Waiting Queue會被大量線程執(zhí)行CAS操作,為了降低對尾部元素的競爭仍律,將Waiting Queue 拆分成ContentionList 和 EntryList 二個(gè)隊(duì)列, JVM將一部分線程移到EntryList 作為準(zhǔn)備進(jìn)OnDeck的預(yù)備線程嘿悬。另外說明幾點(diǎn):
1、所有請求鎖的線程首先被放在ContentionList這個(gè)競爭隊(duì)列中;
2水泉、Contention List 中那些有資格成為候選資源的線程被移動(dòng)到 Entry List 中;
3善涨、任意時(shí)刻,最多只有一個(gè)線程正在競爭鎖資源草则,該線程被成為 OnDeck;
4钢拧、當(dāng)前已經(jīng)獲取到所資源的線程被稱為 Owner;
5、處于 ContentionList炕横、EntryList源内、WaitSet 中的線程都處于阻塞狀態(tài),該阻塞是由操作系統(tǒng)來完成的(Linux 內(nèi)核下采用 pthread_mutex_lock 內(nèi)核函數(shù)實(shí)現(xiàn)的);
6份殿、作為Owner 的A 線程執(zhí)行過程中膜钓,可能調(diào)用wait 釋放鎖,這個(gè)時(shí)候A線程進(jìn)入 Wait Set , 等待被喚醒卿嘲。
4.6 鎖消除
為了保證數(shù)據(jù)的完整性颂斜,在進(jìn)行操作時(shí)需要對這部分操作進(jìn)行同步控制,但是在有些情況下拾枣,JVM檢測到不可能存在共享數(shù)據(jù)競爭沃疮,這是JVM會對這些同步鎖進(jìn)行鎖消除。
鎖消除的依據(jù)是逃逸分析的數(shù)據(jù)支持
如果不存在競爭放前,為什么還需要加鎖呢忿磅?所以鎖消除可以節(jié)省毫無意義的請求鎖的時(shí)間绳慎。變量是否逃逸崔拥,對于虛擬機(jī)來說需要使用數(shù)據(jù)流分析來確定,但是對于程序員來說這還不清楚么辕羽?在明明知道不存在數(shù)據(jù)競爭的代碼塊前加上同步嗎似扔?但是有時(shí)候程序并不是我們所想的那樣吨些?雖然沒有顯示使用鎖,但是在使用一些JDK的內(nèi)置API時(shí)炒辉,如StringBuffer豪墅、Vector、HashTable等黔寇,這個(gè)時(shí)候會存在隱形的加鎖操作偶器。比如StringBuffer的append()方法,Vector的add()方法:
public void vectorTest(){
Vector<String> vector = new Vector<String>();
for(int i = 0 ; i < 10 ; i++){
vector.add(i + "");
}
System.out.println(vector);
}
在運(yùn)行這段代碼時(shí),JVM可以明顯檢測到變量vector沒有逃逸出方法vectorTest()之外屏轰,所以JVM可以大膽地將vector內(nèi)部的加鎖操作消除颊郎。
4.7 鎖粗化
在使用同步鎖的時(shí)候,需要讓同步塊的作用范圍盡可能小——僅在共享數(shù)據(jù)的實(shí)際作用域中才進(jìn)行同步霎苗,這樣做的目的是 為了使需要同步的操作數(shù)量盡可能縮小姆吭,如果存在鎖競爭,那么等待鎖的線程也能盡快拿到鎖唁盏。
在大多數(shù)的情況下内狸,上述觀點(diǎn)是正確的。但是如果一系列的連續(xù)加鎖解鎖操作厘擂,可能會導(dǎo)致不必要的性能損耗昆淡,所以引入鎖粗化的概念。
鎖粗話概念比較好理解驴党,就是將多個(gè)連續(xù)的加鎖瘪撇、解鎖操作連接在一起获茬,擴(kuò)展成一個(gè)范圍更大的鎖
如上面實(shí)例:
vector每次add的時(shí)候都需要加鎖操作港庄,JVM檢測到對同一個(gè)對象(vector)連續(xù)加鎖、解鎖操作恕曲,會合并一個(gè)更大范圍的加鎖鹏氧、解鎖操作,即加鎖解鎖操作會移到for循環(huán)之外佩谣。
synchronized的可重入性
從互斥鎖的設(shè)計(jì)上來說把还,當(dāng)一個(gè)線程試圖操作一個(gè)由其他線程持有的對象鎖的臨界資源時(shí),將會處于阻塞狀態(tài)茸俭,但當(dāng)一個(gè)線程再次請求自己持有對象鎖的臨界資源時(shí)吊履,這種情況屬于重入鎖,請求將會成功调鬓,在java中synchronized是基于原子性的內(nèi)部鎖機(jī)制艇炎,是可重入的,因此在一個(gè)線程調(diào)用synchronized方法的同時(shí)在其方法體內(nèi)部調(diào)用該對象另一個(gè)synchronized方法腾窝,也就是說一個(gè)線程得到一個(gè)對象鎖后再次請求該對象鎖缀踪,是允許的,這就是synchronized的可重入性虹脯。如下:
public class AccountingSync implements Runnable{
static AccountingSync instance=new AccountingSync();
static int i=0;
static int j=0;
@Override
public void run() {
for(int j=0;j<1000000;j++){
//this,當(dāng)前實(shí)例對象鎖
synchronized(this){
i++;
increase();//synchronized的可重入性
}
}
}
public synchronized void increase(){
j++;
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
正如代碼所演示的驴娃,在獲取當(dāng)前實(shí)例對象鎖后進(jìn)入synchronized代碼塊執(zhí)行同步代碼,并在代碼塊中調(diào)用了當(dāng)前實(shí)例對象的另外一個(gè)synchronized方法循集,再次請求當(dāng)前實(shí)例鎖時(shí)唇敞,將被允許,進(jìn)而執(zhí)行方法體代碼,這就是重入鎖最直接的體現(xiàn)疆柔,需要特別注意另外一種情況蕉世,當(dāng)子類繼承父類時(shí),子類也是可以通過可重入鎖調(diào)用父類的同步方法婆硬。注意由于synchronized是基于monitor實(shí)現(xiàn)的狠轻,因此每次重入,monitor中的計(jì)數(shù)器仍會加1彬犯。
線程中斷與synchronized
線程中斷:正如中斷二字所表達(dá)的意義向楼,在線程運(yùn)行(run方法)中間打斷它,在Java中谐区,提供了以下3個(gè)有關(guān)線程中斷的方法:
//中斷線程(實(shí)例方法)
public void Thread.interrupt();
//判斷線程是否被中斷(實(shí)例方法)
public boolean Thread.isInterrupted();
//判斷是否被中斷并清除當(dāng)前中斷狀態(tài)(靜態(tài)方法)
public static boolean Thread.interrupted();
當(dāng)一個(gè)線程處于被阻塞狀態(tài)或者試圖執(zhí)行一個(gè)阻塞操作時(shí)湖蜕,使用Thread.interrupt()方式中斷該線程,注意此時(shí)將會拋出一個(gè)InterruptedException的異常宋列,同時(shí)中斷狀態(tài)將會被復(fù)位(由中斷狀態(tài)改為非中斷狀態(tài))昭抒,如下代碼將演示該過程:
public class InterruputSleepThread3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
//while在try中,通過異常中斷就可以退出run循環(huán)
try {
while (true) {
//當(dāng)前線程處于阻塞狀態(tài)炼杖,異常必須捕捉處理灭返,無法往外拋出
TimeUnit.SECONDS.sleep(2);
}
} catch (InterruptedException e) {
System.out.println("Interruted When Sleep");
boolean interrupt = this.isInterrupted();
//中斷狀態(tài)被復(fù)位
System.out.println("interrupt:"+interrupt);
}
}
};
t1.start();
TimeUnit.SECONDS.sleep(2);
//中斷處于阻塞狀態(tài)的線程
t1.interrupt();
/**
* 輸出結(jié)果:
Interruted When Sleep
interrupt:false
*/
}
}
如上述代碼所示,我們創(chuàng)建一個(gè)線程坤邪,并在線程中調(diào)用了sleep方法從而使用線程進(jìn)入阻塞狀態(tài)熙含,啟動(dòng)線程后,調(diào)用線程實(shí)例對象的interrupt方法中斷阻塞異常艇纺,并拋出InterruptedException異常怎静,此時(shí)中斷狀態(tài)也將被復(fù)位。
等待喚醒機(jī)制與synchronized
所謂等待喚醒機(jī)制本篇主要指的是notify/notifyAll和wait方法黔衡,在使用這3個(gè)方法時(shí)蚓聘,必須處于synchronized代碼塊或者synchronized方法中,否則就會拋出IllegalMonitorStateException異常盟劫,這是因?yàn)檎{(diào)用這幾個(gè)方法前必須拿到當(dāng)前對象的監(jiān)視器monitor對象夜牡,也就是說notify/notifyAll和wait方法依賴于monitor對象,在前面的分析中捞高,我們知道m(xù)onitor 存在于對象頭的Mark Word 中(存儲monitor引用指針)氯材,而synchronized關(guān)鍵字可以獲取 monitor ,這也就是為什么notify/notifyAll和wait方法必須在synchronized代碼塊或者synchronized方法調(diào)用的原因硝岗。
synchronized (obj) {
obj.wait();
obj.notify();
obj.notifyAll();
}
需要特別理解的一點(diǎn)是氢哮,與sleep方法不同的是wait方法調(diào)用完成后,線程將被暫停型檀,但wait方法將會釋放當(dāng)前持有的監(jiān)視器鎖(monitor)冗尤,直到有線程調(diào)用notify/notifyAll方法后方能繼續(xù)執(zhí)行,而sleep方法只讓線程休眠并不釋放鎖。同時(shí)notify/notifyAll方法調(diào)用后裂七,并不會馬上釋放監(jiān)視器鎖皆看,而是在相應(yīng)的synchronized(){}/synchronized方法執(zhí)行結(jié)束后才自動(dòng)釋放鎖。
sleep(long n)和wait(long n)的區(qū)別
- 1背零、sleep是Thread方法腰吟,而wait是Object方法。
- 2徙瓶、sleep不需要強(qiáng)制和synchronized配合使用毛雇,而wait需要和synchronized配合使用。
- 3侦镇、sleep在睡眠的同時(shí)不會釋放對象鎖灵疮,但wait在等待的同時(shí)會釋放對象鎖。
- 4壳繁、調(diào)用了sleep(long n)和wait(long n)的線程都進(jìn)入了TIMED_WAITING狀態(tài)震捣。
參考:
https://www.cnblogs.com/qingshan-tang/p/12698705.html
http://www.reibang.com/p/09de11d71ef8
https://blog.csdn.net/qq_43719634/article/details/108917522
https://www.cnblogs.com/aspirant/p/11470858.html