Java并发编程之synchronized关键字解析
前言
????????公司加班太狠了,都沒啥時間充電,這周終于結(jié)束了。這次整理了Java并發(fā)編程里面的synchronized關(guān)鍵字,又稱為隱式鎖,與JUC包中的Lock顯示鎖相對應(yīng);這個關(guān)鍵字從Java誕生開始就有,稱之為重量級鎖,自從JDK1.6之后官方對該關(guān)鍵字進(jìn)行優(yōu)化,引入了輕量級鎖和偏向鎖,于是就有了鎖升級的概念。
使用
在代碼中使用這個關(guān)鍵字總共有以下三種:
private static Object object = new Object();private synchronized void function1() {//鎖住當(dāng)前實(shí)例對象}private static synchronized void function2() {//鎖住當(dāng)前class對象,可以認(rèn)為是鎖住當(dāng)前Class文件}public static void main( String[] args ) {synchronized (object) {//鎖住object對象}}1:普通方法同步;
2:靜態(tài)方法同步;
3:同步代碼塊括號中的對象;
使用synchronized關(guān)鍵字進(jìn)行同步,則鎖是儲存于Java對象頭里面的Mark Word。
Java對象頭里面的Mark Word里面主要是存儲對象的hashCode、分代年齡(用于判斷為老年代還是年輕代,在垃圾回收器里面用得到)以及鎖標(biāo)記位。
鎖的升級
在JDK1.6之后,引入了引入了偏向鎖和輕量級鎖的狀態(tài),目的是為了提升鎖的釋放和獲取效率,減少性能的開銷,所以synchronized就有四種級別鎖的狀態(tài),級別從低到高分別是無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖以及重量級鎖狀態(tài);四種狀態(tài)實(shí)質(zhì)上是對象頭儲存的鎖標(biāo)記位不一致,使用CAS更改對象頭的標(biāo)記位進(jìn)行鎖狀態(tài)位;并且在記錄鎖的標(biāo)記位的同時,也會在Mark Word里面記錄鎖線程的ID
無鎖
既在對象頭的Mark Word沒有標(biāo)記鎖狀態(tài)的時候就是無鎖狀態(tài)
偏向鎖
單個線程進(jìn)行訪問或調(diào)用帶synchronized的同步代碼塊或方法時,會在先判斷對象頭里面鎖標(biāo)志位是否有線程ID,如果沒有線程ID的話,將當(dāng)前線程ID寫入進(jìn)去,并且也會在棧幀中的鎖記錄里面進(jìn)行記錄。此時,鎖的狀態(tài)位為偏向鎖,通俗來說可以說是只要單線程訪問同步代碼塊,從無鎖狀態(tài)就會便成為偏向鎖狀態(tài);如果在對象頭里面存在線程ID的話,如果當(dāng)前線程ID是與對象頭里面記錄的線程ID的話,那么就可以直接訪問,不需要使用CAS去進(jìn)行競爭鎖了;不一致的話,那么就會使用CAS進(jìn)行競爭鎖。
當(dāng)其他線程開始競爭偏向鎖的時候,那么持有偏向鎖的線程就去會釋放偏向鎖,供其他線程使用;這時候就出現(xiàn)一種偏向鎖的撤銷概念
偏向鎖的撤銷
偏向鎖的撤銷就是,會先暫停持有偏向鎖的線程,然后去對象頭里面判斷記錄線程ID的線程是否還在處于活動狀態(tài),如果處于非活動狀態(tài),那么就會將Mark Word里面的鎖標(biāo)志位設(shè)置為無鎖狀態(tài);如果處于活動狀態(tài)的話,會先遍歷偏向?qū)ο蟮逆i記錄、棧里面鎖記錄以及對象頭里面Mark Word里面的鎖標(biāo)記位,并且將對象頭中鎖標(biāo)志位設(shè)置為無鎖狀態(tài)或者升級成輕量鎖狀態(tài)的,然后喚醒持有偏向鎖的的線程,繼續(xù)執(zhí)行;
輕量級鎖
加鎖
當(dāng)執(zhí)行同步代碼塊升級為輕量級鎖的時候,會在棧幀中創(chuàng)建一塊用于儲存鎖記錄的空間,并且將對象頭里面Mark Word復(fù)制到記錄中(Displaced Mark Word),線程開始會使用CAS將Mark Word替換為指向鎖記錄的指針,如果獲取成功,那么當(dāng)前線程獲取鎖,如果失敗,采用自旋來獲取鎖,如果自旋獲取鎖失敗,那么會膨脹為重量級鎖。
解鎖
輕量級鎖解鎖會使用CAS將Displaced Mark Word替換回到對象頭中,如果成功了,表示沒有鎖競爭;如果失敗了表示有鎖在競爭,那么就會膨脹成重量級鎖,那么在自旋的獲取鎖的線程就會進(jìn)行線程阻塞;
由于自旋會大量消耗CPU資源,所以一旦升級成為了重量級鎖之后,那么就不會進(jìn)行降級了。
重量級鎖
這個就是線程阻塞了,基本上可以和Lock表現(xiàn)一致了,一旦有線程獲取鎖,其他獲取鎖的線程將會阻塞,釋放鎖之后將會喚醒阻塞線程去競爭鎖
| 鎖 | 優(yōu)點(diǎn) | 缺點(diǎn) | 使用場景 |
| 偏向鎖 | 加鎖和解鎖不需要額外的資源消耗,性能快 | 如果線程之間存在鎖競爭,那么會出現(xiàn)鎖撤銷,暫停線程,比較消耗資源 | 適用于單線程使用場景 |
| 輕量級鎖 | 線程不會阻塞,提高響應(yīng)程度 | 自旋消耗CPU性能,容易升級為重量級鎖 | 適用于同步代碼塊執(zhí)行非??斓?/td> |
| 重量級鎖 | 線程不會自旋,避免過多消耗CPU資源 | 線程阻塞,響應(yīng)時間緩慢 | 提高吞吐量,同步代碼塊執(zhí)行較長 |
等待/通知機(jī)制
這個之前是有篇講過線程之間的共享協(xié)作:線程協(xié)作?這個里面提到過如何使用
現(xiàn)在看看底層是如何運(yùn)行的執(zhí)行,我們先看一段代碼
public class SynchronziedDemo {public static Object object = new Object();public static void main(String[] args) {synchronized (object) {}m();}public static synchronized void m() {} }將這段代碼編譯后使用javap -v命令進(jìn)行反編譯
會得到class文件的編譯后的c代碼:
同步代碼塊使用的事monitorenter(獲取鎖)和monitorexit(釋放鎖)指令,同步放上是使用ACC_SYNCRONIZED來完成的。
無論是哪個本質(zhì)上其實(shí)是個monitor監(jiān)視器進(jìn)行完成的,每個對象都會有一個監(jiān)視器,線程需先獲取到monitor監(jiān)視器才能訪問同步代碼塊或者方法,而沒有獲取到的線程就會進(jìn)入自旋,升級為重量級鎖,然后會進(jìn)入到阻塞狀態(tài),這時候會有一個同步隊(duì)列(SynchronizedQueue),阻塞的線程會加入到這個隊(duì)列里面,等待獲取到監(jiān)視器的線程調(diào)用monitorexit指定后,會喚醒同步隊(duì)列里面的等待線程。
等待/通知機(jī)制里面對了一個WaitQueue即等待隊(duì)列,案例:
public class WaitNotify {public static Object object = new Object();public static void main(String[] args) {new Thread(new WaitClass()).start();new Thread(new NotifyClass()).start();}static class NotifyClass implements Runnable {@Overridepublic void run() {synchronized (object) {try {TimeUnit.SECONDS.sleep(2L);System.out.println(Thread.currentThread() + "notify start...");object.notifyAll();System.out.println(Thread.currentThread() + "notify end...");} catch (InterruptedException e) {e.printStackTrace();}}}}static class WaitClass implements Runnable {@Overridepublic void run() {synchronized (object) {System.out.println(Thread.currentThread() + "wait start....");try {object.wait();System.out.println(Thread.currentThread() + "wait end....");} catch (InterruptedException e) {e.printStackTrace();}}}} }?打印日志如下:
Thread[Thread-0,5,main]wait start....
Thread[Thread-1,5,main]notify start...
Thread[Thread-1,5,main]notify end...
Thread[Thread-0,5,main]wait end....
對于wait線程獲取到object對象的監(jiān)視器之后,調(diào)用wait方法后進(jìn)入等待隊(duì)列(WaitQueue),然后釋放監(jiān)視器。
對于notify線程來說,先獲取到object對象監(jiān)視器之后,然后調(diào)用notifyAll方法,將WaitQueue里面的所有等待線程同步到同步隊(duì)列中,(如果是調(diào)用notify方法就會值同步一個線程并非所有線程),然后釋放監(jiān)視器,就會喚醒同步列隊(duì)中的線程。
對于wait線程,在同步隊(duì)列唄喚醒后,會重新獲取監(jiān)視器,然后繼續(xù)執(zhí)行wait方法后面的代碼。
這樣就完成一個等待通知的機(jī)制了。
總結(jié)
以上是生活随笔為你收集整理的Java并发编程之synchronized关键字解析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos升级glibc(升级到 2.
- 下一篇: Java并发编程之显式锁(Lock)使用