先綜述個結(jié)論:
一般說的synchronized用來做多線程同步功能谱俭,其實synchronized只是提供多線程互斥奉件,而對象的wait()和notify()方法才提供線程的同步功能。
一般說synchronized是加鎖昆著,或者說是加對象鎖县貌,其實對象鎖只是synchronized在實現(xiàn)鎖機制中的一種鎖(重量鎖,用這種方式互斥線程開銷大所以叫重量鎖凑懂,或者叫對象monitor)煤痕,而synchronized的鎖機制會根據(jù)線程競爭情況在運行會有偏向鎖、輕量鎖接谨、對象鎖摆碉,自旋鎖(或自適應(yīng)自旋鎖)等,總之脓豪,synchronized可以認(rèn)為是一個幾種鎖過程的封裝巷帝。
原理
通常說的synchronized在方法或塊上加鎖,這里的鎖就是對象鎖(當(dāng)然也可以在類上面)扫夜,或者叫重量鎖楞泼,在JVM中又叫對象監(jiān)視器(Monitor)驰徊,就是對象來監(jiān)視線程的互斥。
先來回顧一下對象在堆里的邏輯結(jié)構(gòu):
對象頭里的結(jié)構(gòu)大致如此:
其中Tag的2bit用來顯示鎖類型堕阔。通常我們說synchronized的對象鎖棍厂,就是這里Tag=10時的monitor對象,這里的Monitor address就是這個monitor對象(就是重量鎖)的地址印蔬。
當(dāng)多個線程同時請求synchronized方法或塊時勋桶,monitor會設(shè)置幾個虛擬邏輯數(shù)據(jù)結(jié)構(gòu)來管理這些多線程。下圖是簡化了的管理結(jié)構(gòu)侥猬。
新請求的線程會首先被加入到線程排隊隊列中例驹,線程阻塞,當(dāng)某個擁有鎖的線程unlock之后退唠,則排隊隊列里的線程競爭上崗(synchronized是不公平競爭鎖鹃锈,下面還會講到)。如果運行的線程調(diào)用對象的wait()后就釋放鎖并進入wait線程集合那邊瞧预,當(dāng)調(diào)用對象的notify()或notifyall()后屎债,wait線程就到排隊那邊。這是大致的邏輯垢油。
同時再看看線程的狀態(tài)圖
Blocked就是阻塞狀態(tài)盆驹。
wait()和sleep()最大的不同在于wait()會釋放對象鎖,而sleep()不會滩愁!wait躯喇、sleep、yield區(qū)別如下:
似乎講到這里硝枉,synchronized鎖和wait()廉丽、notify()來實現(xiàn)多線程同步就完成了。
但是妻味,自旋鎖或自適應(yīng)自旋鎖:
因為線程阻塞后進入排隊隊列和喚醒都需要CPU從用戶態(tài)轉(zhuǎn)為核心態(tài)正压,尤其頻繁的阻塞和喚醒對CPU來說是負(fù)荷很重的工作。同時統(tǒng)計發(fā)現(xiàn)责球,很多對象鎖的鎖定狀態(tài)只會持續(xù)很短的一段時間焦履,例如一個線程切換周期,這樣的話在很短的時間內(nèi)阻塞線程又很快喚醒線程顯然不值得棕诵,所以引入了自旋鎖概念裁良。
所謂“自旋”,就monitor并不把線程阻塞放入排隊隊列校套,而是去執(zhí)行一個無意義的循環(huán)价脾,循環(huán)結(jié)束后看看是否鎖已釋放并直接進行競爭上崗步驟,如果競爭不到繼續(xù)自旋循環(huán)笛匙,循環(huán)過程中線程的狀態(tài)一直處于running狀態(tài)侨把。明顯自旋鎖使得synchronized的對象鎖方式在線程之間引入了不公平犀变。但是這樣可以保證大吞吐率和執(zhí)行效率。
不過雖然自旋鎖方式省去了阻塞線程的時間和空間(隊列的維護等)開銷秋柄,但是長時間自旋也是很低效的获枝。所以自旋的次數(shù)一般控制在一個范圍內(nèi),例如10,50等骇笔,在超出這個范圍后省店,線程就進入排隊隊列。
自適應(yīng)自旋鎖笨触,就是自旋的次數(shù)是通過JVM在運行時收集的統(tǒng)計信息懦傍,動態(tài)調(diào)整自旋鎖的自旋次數(shù)上界。
講到這里似乎synchronized鎖的過程更加豐滿了芦劣。
不過synchronized在運行過程中不是一下子就到對象鎖這個級別的粗俱,它根據(jù)線程競爭情況會經(jīng)過幾次升級變化。這里就出現(xiàn)了另外幾種鎖虚吟。
輕量鎖和偏向鎖
當(dāng)多線程環(huán)境進入synchronized區(qū)域的線程沒競爭時寸认,JVM并不會馬上創(chuàng)建對象鎖,而是用輕量鎖或偏向鎖串慰。
不過需要明確的是偏塞,輕量鎖和偏向鎖,都不能代替重量鎖邦鲫,只不過是在沒有多線程競爭時烛愧,沒必要用重量鎖而無畏的消耗資源。但是一旦出現(xiàn)了多線程競爭時掂碱,synchronized區(qū)域的輕量鎖或偏向鎖都會立即升級為重量鎖。
輕量鎖或偏向鎖使用的條件是進入synchronized區(qū)域時沒有其他任何其他線程在使用慎冤。
這時線程t訪問對象的synchronized區(qū)域時疼燥,對象頭的標(biāo)志位Tag狀態(tài)為01,以及還有1位的偏向信息用于記錄這個對象是否可用偏向鎖蚁堤。然后t在對象上申請輕量鎖時醉者,若偏向信息為0,表明當(dāng)前對象還未加鎖披诗,或加過偏向鎖(加過撬即,注意是加過偏向鎖的對象只能被同樣的線程加鎖,如果不同的線程想要獲取鎖呈队,需要先將偏向鎖升級為輕量鎖剥槐,稍后會講到),在判斷對當(dāng)前對象確實沒有被任何其他線程鎖住后宪摧,即可以在該對象上加輕量鎖粒竖。
加輕量鎖的過程很簡單:在當(dāng)前線程的棧幀(stack?frame)中生成一個鎖記錄(lock?record)颅崩,這個鎖記錄比前面說的那個對象鎖(管理線程隊列的monitor)簡單多了,它只是對象頭的一個拷貝蕊苗。然后把對象頭里的tag改成00沿后,并把這個棧幀里的lock?record地址放入對象頭里。若操作成功朽砰,那就完成了輕量鎖操作尖滚。如果不成功,說明有線程在競爭瞧柔,則需要在當(dāng)前對象上生成重量鎖來進行多線程同步漆弄,然后將Tag狀態(tài)改為10,并生成Monitor對象(重量鎖對象)非剃,對象頭里也會放入Monitor對象的地址置逻。最后將當(dāng)前線程t排隊隊列中。
輕量鎖的解鎖過程也很簡單就是把棧幀里剛才的那個lock?record拷貝到對象頭里备绽,若替換成功券坞,則解鎖完成,若替換不成功肺素,表示在當(dāng)前線程持有鎖的這段時間內(nèi)恨锚,其他線程也競爭過鎖,并且發(fā)生了鎖升級為重量鎖倍靡,這時需要去Monitor的等待隊列中喚醒一個線程去重新競爭鎖猴伶。
偏向鎖是比輕量鎖還輕量的鎖機制。當(dāng)synchronized區(qū)域長期都由同一個線程加鎖塌西、解鎖時他挎,jvm就用偏向鎖來做,它的加鎖解鎖比輕量鎖操作起來指令更加簡化捡需。不過一旦有其他線程使用synchronized區(qū)域办桨,即使沒有線程間競爭,也會把偏向鎖升級為輕量鎖站辉,當(dāng)然如果發(fā)生線程競爭就再升級為對象鎖呢撞。
鎖的公平與不公平:公平鎖是指線程獲得鎖的順序按照fifo的原則,先排隊的先得饰剥。非公平鎖指每個線程都先要競爭鎖殊霞,不管排隊先后,所以后到的線程有可能無需進入等待隊列直接競爭到鎖汰蓉。非公平鎖雖然可能導(dǎo)致某些線程饑餓绷蹲,但是鎖的吞吐率是公平鎖好幾倍,synchronized是一個典型的非公平鎖方案顾孽,而且沒法做成公平鎖瘸右。