国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

java線程-synchronized詳解

這篇具有很好參考價(jià)值的文章主要介紹了java線程-synchronized詳解。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

解決線程原子性問(wèn)題,最常見(jiàn)的手段就是加鎖,Java提供了兩種加鎖的方式,一個(gè)synchronized隱式鎖,另外一個(gè)是通過(guò)J.U.C框架提供的Lock顯式加鎖。本文主要介紹一個(gè)Synchronized的實(shí)現(xiàn)方式。

synchronized概述

synchronized關(guān)鍵字解決的是多個(gè)線程之間訪問(wèn)資源的同步性,synchronized 翻譯為中文的意思是同步,也稱(chēng)之為”同步鎖“。

synchronized的作用是保證在同一時(shí)刻, 被修飾的代碼塊或方法只會(huì)有一個(gè)線程執(zhí)行,以達(dá)到保證并發(fā)安全的效果。

synchronized的使用方式

基本語(yǔ)法

synchronized有兩個(gè)作用范圍:方法和局部代碼塊,代碼示例如下

public class SynchronizedDemo {
?
 ?private int v;
 ?private static int a;
 ?private final Object lock = new Object();
?
 ?// 修飾非靜態(tài)方法 對(duì)象鎖
 ?public synchronized void add(int value) {
 ? ?v += value; // 臨界區(qū)
  }
?
 ?public void sub(int value) {
 ? ?// 修飾局部代碼塊 對(duì)象鎖
 ? ?synchronized (lock) {
 ? ? ?v -= value; // 臨界區(qū)
 ?  }
  }
?
 ?// 修飾靜態(tài)方法 類(lèi)鎖
 ?public static synchronized void multi(int value) {
 ? ?a *= value; // 臨界區(qū)
  }
?
 ?public static void div(int value) {
 ? ?// 修飾局部代碼塊 類(lèi)鎖
 ? ?synchronized (SynchronizedDemo.class) {
 ? ? ?a /= value; // 臨界區(qū)
 ?  }
  }
} 
復(fù)制代碼

java編譯器會(huì)在synchronized修飾的方法或代碼塊前后自動(dòng)Lock,unlock。

  1. synchronized修飾代碼塊,鎖定是個(gè)obj對(duì)象,或者是一個(gè)類(lèi),sychronized(this.class)
  2. synchronized修飾靜態(tài)方法,鎖定是當(dāng)前類(lèi)的class對(duì)象
  3. synchronized修飾非靜態(tài)方法,鎖定的是當(dāng)前實(shí)例對(duì)象this。

synchronized底層實(shí)現(xiàn)原理

synchronized對(duì)應(yīng)的字節(jié)碼

使用javap -verbose SynchronizedDemo查看字節(jié)碼文件

  public synchronized void add(int);
 ?  descriptor: (I)V
 ?  flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
 ?  Code:
 ? ?  stack=3, locals=2, args_size=2
 ? ? ? ? 0: aload_0
 ? ? ? ? 1: dup
 ? ? ? ? 2: getfield ? ?  #4 ? ? ? ? ? ? ? ?  // Field v:I
 ? ? ? ? 5: iload_1
 ? ? ? ? 6: iadd
 ? ? ? ? 7: putfield ? ?  #4 ? ? ? ? ? ? ? ?  // Field v:I
 ? ? ?  10: return
 ? ?  LineNumberTable:
 ? ? ?  line 10: 0
 ? ? ?  line 11: 10
 ? ? ? ?
  public void sub(int);
 ?  descriptor: (I)V
 ?  flags: (0x0001) ACC_PUBLIC
 ?  Code:
 ? ?  stack=3, locals=4, args_size=2
 ? ? ? ? 0: aload_0
 ? ? ? ? 1: getfield ? ?  #3 ? ? ? ? ? ? ? ?  // Field lock:Ljava/lang/Object;
 ? ? ? ? 4: dup
 ? ? ? ? 5: astore_2
 ? ? ? ? 6: monitorenter
 ? ? ? ? 7: aload_0
 ? ? ? ? 8: dup
 ? ? ? ? 9: getfield ? ?  #4 ? ? ? ? ? ? ? ?  // Field v:I
 ? ? ?  12: iload_1
 ? ? ?  13: isub
 ? ? ?  14: putfield ? ?  #4 ? ? ? ? ? ? ? ?  // Field v:I
 ? ? ?  17: aload_2
 ? ? ?  18: monitorexit
 ? ? ?  19: goto ? ? ? ?  27
 ? ? ?  22: astore_3
 ? ? ?  23: aload_2
 ? ? ?  24: monitorexit
 ? ? ?  25: aload_3
 ? ? ?  26: athrow
 ? ? ?  27: return
 ? ?  Exception table:
 ? ? ? ? from ?  to  target type
 ? ? ? ? ? ? 7 ?  19 ?  22 ? any
 ? ? ? ? ?  22 ?  25 ?  22 ? any
 ? ?  LineNumberTable:
 ? ? ?  line 14: 0
 ? ? ?  line 15: 7
 ? ? ?  line 16: 17
 ? ? ?  line 17: 27
 ? ? ? ?
  public static synchronized void multi(int);
 ?  descriptor: (I)V
 ?  flags: (0x0029) ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
 ?  Code:
 ? ?  stack=2, locals=1, args_size=1
 ? ? ? ? 0: getstatic ? ? #5 ? ? ? ? ? ? ? ?  // Field a:I
 ? ? ? ? 3: iload_0
 ? ? ? ? 4: imul
 ? ? ? ? 5: putstatic ? ? #5 ? ? ? ? ? ? ? ?  // Field a:I
 ? ? ? ? 8: return
 ? ?  LineNumberTable:
 ? ? ?  line 20: 0
 ? ? ?  line 21: 8
?
  public static void div(int);
 ?  descriptor: (I)V
 ?  flags: (0x0009) ACC_PUBLIC, ACC_STATIC
 ?  Code:
 ? ?  stack=2, locals=3, args_size=1
 ? ? ? ? 0: ldc ? ? ? ? ? #6 ? ? ? ? ? ? ? ?  // class com/shawn/study/deep/in/java/concurrency/thread/SynchronizedDemo
 ? ? ? ? 2: dup
 ? ? ? ? 3: astore_1
 ? ? ? ? 4: monitorenter
 ? ? ? ? 5: getstatic ? ? #5 ? ? ? ? ? ? ? ?  // Field a:I
 ? ? ? ? 8: iload_0
 ? ? ? ? 9: idiv
 ? ? ?  10: putstatic ? ? #5 ? ? ? ? ? ? ? ?  // Field a:I
 ? ? ?  13: aload_1
 ? ? ?  14: monitorexit
 ? ? ?  15: goto ? ? ? ?  23
 ? ? ?  18: astore_2
 ? ? ?  19: aload_1
 ? ? ?  20: monitorexit
 ? ? ?  21: aload_2
 ? ? ?  22: athrow
 ? ? ?  23: return
 ? ?  Exception table:
 ? ? ? ? from ?  to  target type
 ? ? ? ? ? ? 5 ?  15 ?  18 ? any
 ? ? ? ? ?  18 ?  21 ?  18 ? any
 ? ?  LineNumberTable:
 ? ? ?  line 24: 0
 ? ? ?  line 25: 5
 ? ? ?  line 26: 13
 ? ? ?  line 27: 23 ? ? ? ?
復(fù)制代碼

從上述展示的字節(jié)碼可以看出:

add()函數(shù)對(duì)應(yīng)的字節(jié)碼如下所示。實(shí)際上,編譯器只不過(guò)是在函數(shù)的flags中添加了ACC_SYNCHRONIZED標(biāo)記而已,其他部分跟沒(méi)有添加synchronized的add()函數(shù)的字節(jié)碼相同。

add()函數(shù)對(duì)應(yīng)的字節(jié)碼如下所示。字節(jié)碼通過(guò)monitorenter和monitorexit來(lái)標(biāo)記synchronized的作用范圍。除此之外,對(duì)于以下字節(jié)碼,我們有點(diǎn)需要解釋。其一,以下字節(jié)碼中有兩個(gè)monitorexit,添加第二個(gè)monitorexit的目的是為了在代碼拋出異常時(shí)仍然能解鎖。其二,前面講到,synchronized可以選擇指定使用哪個(gè)對(duì)象的Monitor鎖。具體使用哪個(gè)對(duì)象的Monitor鎖,在字節(jié)碼中,通過(guò)monitorenter前面的幾行字節(jié)碼來(lái)指定。

synchronized關(guān)鍵字底層使用的鎖叫做Monitor鎖。但是,我們無(wú)法直接創(chuàng)建和使用Monitor鎖。Monitor鎖是寄生存在的,每個(gè)對(duì)象都會(huì)擁有一個(gè)Monitor鎖。如果我們想要使用一個(gè)新的Monitor鎖,我們只需要使用一個(gè)新的對(duì)象,并在synchronized關(guān)鍵字后,附帶聲明要使用哪個(gè)對(duì)象的Monitor鎖即可。

  • 當(dāng)使用sychronized修飾方法的時(shí)候,編譯器只不過(guò)是在函數(shù)的flags中添加了ACC_SYNCHRONIZED標(biāo)記而已,其他部分跟沒(méi)有添加synchronized的函數(shù)的字節(jié)碼相同。

  • 當(dāng)使用synchronized修飾局部代碼塊的時(shí)候,字節(jié)碼通過(guò)monitorenter和monitorexit來(lái)標(biāo)記synchronized的作用范圍。但有兩點(diǎn)需要再解釋一下

    • synchronized關(guān)鍵字底層使用的鎖叫做Monitor鎖。但是,我們無(wú)法直接創(chuàng)建和使用Monitor鎖。Monitor鎖是寄生存在的,每個(gè)對(duì)象都會(huì)擁有一個(gè)Monitor鎖,在字節(jié)碼中,通過(guò)monitorenter前面的幾行字節(jié)碼來(lái)指定。
    • 以下字節(jié)碼中有兩個(gè)monitorexit,添加第二個(gè)monitorexit的目的是為了在代碼拋出異常時(shí)仍然能解鎖。

monitor鎖實(shí)現(xiàn)原理

synchronized在底層使用不同的鎖來(lái)實(shí)現(xiàn),重量級(jí)鎖,輕量級(jí)鎖,偏向鎖等。

實(shí)際上,synchronized使用的重量級(jí)鎖,就是前面提到的對(duì)象上的Monitor鎖。JVM有不同的實(shí)現(xiàn)版本,因此,Monitor鎖也有不同的實(shí)現(xiàn)方式。在Hotspot JVM實(shí)現(xiàn)中,Monitor鎖對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)為ObjectMonitor類(lèi)。因?yàn)镠otspot JVM是用C++實(shí)現(xiàn)的,所以,ObjectMonitor也是用C++代碼定義的。ObjectMonitor包含的代碼很多,我們只羅列一些與其基本實(shí)現(xiàn)原理相關(guān)的成員變量,如下所示。

class ObjectMonitor {
 ?void* ?volatile _object; // 該Monitor鎖所屬的對(duì)象
 ?void* ?volatile _owner; ?// 獲取到該Monitor鎖的線程
 ?ObjectWaiter* volatile _EntryList; // 存儲(chǔ)等待被喚醒的線程
 ?ObjectWaiter* volatile _cxq ; // 沒(méi)有獲取到鎖的線程
 ?ObjectWaiter* volatile _WaitSet; // 存儲(chǔ)調(diào)用了wait()的線程
}
復(fù)制代碼

monitor如何與對(duì)象關(guān)聯(lián)

_object表示該Monitor鎖所屬的對(duì)象,但是如何通過(guò)對(duì)象來(lái)找到對(duì)應(yīng)的Monitor鎖呢?對(duì)象的存儲(chǔ)結(jié)構(gòu)如下:

java線程-synchronized詳解

其中Mark Word是個(gè)可變字段,根據(jù)不同的場(chǎng)景記錄不同的信息,monitor鎖的信息就是記錄在此。

monitor如何實(shí)現(xiàn)加鎖,解鎖

ObjectMonitor Enter方法

互斥鎖的基本功能:

  • 多個(gè)線程競(jìng)爭(zhēng)獲取鎖;
  • 沒(méi)有獲取到鎖的線程排隊(duì)等待獲取鎖;
  • 鎖釋放之后會(huì)通知排隊(duì)等待鎖的線程去競(jìng)爭(zhēng)鎖;
  • 沒(méi)有獲取鎖的線程會(huì)阻塞,并且對(duì)應(yīng)的內(nèi)核線程不再分配時(shí)間片;
  • 阻塞線程獲取到鎖之后取消阻塞,并且對(duì)應(yīng)的內(nèi)核線程恢復(fù)分配時(shí)間片。

其中加鎖源代碼如下:

void ObjectMonitor::EnterI(TRAPS) {
  ...
 ?// Try the lock - TATAS
 ?if (TryLock (Self) > 0) {
 ? ?assert(_succ != Self, "invariant");
 ? ?assert(_owner == Self, "invariant");
 ? ?assert(_Responsible != Self, "invariant");
 ? ?return;
  }
  ...
 ?// We try one round of spinning *before* enqueueing Self.
 ?//
 ?// If the _owner is ready but OFFPROC we could use a YieldTo()
 ?// operation to donate the remainder of this thread's quantum
 ?// to the owner.  This has subtle but beneficial affinity
 ?// effects.
?
 ?if (TrySpin (Self) > 0) {
 ? ?assert(_owner == Self, "invariant");
 ? ?assert(_succ != Self, "invariant");
 ? ?assert(_Responsible != Self, "invariant");
 ? ?return;
  }
  ...
 ?ObjectWaiter node(Self);
 ?// Push "Self" onto the front of the _cxq.
 ?// Once on cxq/EntryList, Self stays on-queue until it acquires the lock.
 ?// Note that spinning tends to reduce the rate at which threads
 ?// enqueue and dequeue on EntryList|cxq.
 ?ObjectWaiter * nxt;
 ?for (;;) {
 ? ?node._next = nxt = _cxq;
 ? ?if (Atomic::cmpxchg(&node, &_cxq, nxt) == nxt) break;
?
 ? ?// Interference - the CAS failed because _cxq changed.  Just retry.
 ? ?// As an optional optimization we retry the lock.
 ? ?if (TryLock (Self) > 0) {
 ? ? ?assert(_succ != Self, "invariant");
 ? ? ?assert(_owner == Self, "invariant");
 ? ? ?assert(_Responsible != Self, "invariant");
 ? ? ?return;
 ?  }
  }
  ...
 ?for (;;) {
 ? ?if (TryLock(Self) > 0) break;
 ?  ...
 ? ?if ((SyncFlags & 2) && _Responsible == NULL) {
 ? ? ?Atomic::replace_if_null(Self, &_Responsible);
 ?  }
 ? ?// park self
 ? ?if (_Responsible == Self || (SyncFlags & 1)) {
 ? ? ?TEVENT(Inflated enter - park TIMED);
 ? ? ?Self->_ParkEvent->park((jlong) recheckInterval);
 ? ? ?// Increase the recheckInterval, but clamp the value.
 ? ? ?recheckInterval *= 8;
 ? ? ?if (recheckInterval > MAX_RECHECK_INTERVAL) {
 ? ? ? ?recheckInterval = MAX_RECHECK_INTERVAL;
 ? ?  }
 ?  } else {
 ? ? ?TEVENT(Inflated enter - park UNTIMED);
 ? ? ?Self->_ParkEvent->park();
 ?  }
?
 ? ?if (TryLock(Self) > 0) break;
 ?  ...
  }
  ...
 ?if (_Responsible == Self) {
 ? ?_Responsible = NULL;
  }
 ?// 善后處理,比如將當(dāng)前線程從等待隊(duì)列 CXQ 中移除
  ...
}
?
復(fù)制代碼

多個(gè)線程競(jìng)爭(zhēng)獲取鎖

多個(gè)線程同時(shí)請(qǐng)求獲取Monitor鎖時(shí),JVM會(huì)通過(guò)CAS操作,先檢查_owner是否是null,如果_owner是null,再將自己的Thread對(duì)象的地址賦值給_owner,那么誰(shuí)就獲取到了monitor鎖。

源代碼:

int ObjectMonitor::TryLock (Thread * Self) {
 ? for (;;) {
 ? ? ?void * own = _owner ;
 ? ? ?if (own != NULL) return 0 ;
 ? ? ?if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
 ? ? ? ? // Either guarantee _recursions == 0 or set _recursions = 0.
 ? ? ? ? assert (_recursions == 0, "invariant") ;
 ? ? ? ? assert (_owner == Self, "invariant") ;
 ? ? ? ? // CONSIDER: set or assert that OwnerIsThread == 1
 ? ? ? ? return 1 ;
 ? ?  }
 ? ? ?// The lock had been free momentarily, but we lost the race to the lock.
 ? ? ?// Interference -- the CAS failed.
 ? ? ?// We can either return -1 or retry.
 ? ? ?// Retry doesn't make as much sense because the lock was just acquired.
 ? ? ?if (true) return -1 ;
 ? }
}
復(fù)制代碼

源碼中有段需要注意的是:先檢查再執(zhí)行這類(lèi)復(fù)合操作是非線程安全的,那么就會(huì)存在多個(gè)線程有可能同時(shí)檢查到_owner為null的情況,然后都去改變_owner。為了解決這個(gè)問(wèn)題,JVM采用CPU提供的cmpxchg_ptr指令,通過(guò)給總線加鎖的方式,來(lái)保證了以上CAS操作的線程安全性。

沒(méi)有獲取到鎖的線程排隊(duì)等待獲取鎖

多個(gè)線程競(jìng)爭(zhēng)Monitor鎖,成功獲取到鎖的線程就去執(zhí)行代碼,沒(méi)有獲取到鎖的線程會(huì)放入ObjectMonitor的_cxq單向鏈表中等待鎖

鎖釋放之后會(huì)通知排隊(duì)等待鎖的線程去競(jìng)爭(zhēng)鎖

當(dāng)持有Monitor鎖的線程釋放了鎖之后,JVM會(huì)從_EntryList中取出一個(gè)線程,再取競(jìng)爭(zhēng)Monitor鎖。

如果_EntryList中沒(méi)有線程,JVM會(huì)先將_CXQ中所有線程全部搬移到_EntryList中,然后再?gòu)?code>_EntryList中取線程。

沒(méi)有獲取鎖的線程會(huì)阻塞,并且對(duì)應(yīng)的內(nèi)核線程不再分配時(shí)間片

一個(gè)java線程會(huì)對(duì)應(yīng)一個(gè)內(nèi)核線程。應(yīng)用程序會(huì)將java線程要執(zhí)行的代碼,交給其對(duì)應(yīng)的內(nèi)核線程來(lái)執(zhí)行。內(nèi)核線程在執(zhí)行過(guò)程中,如果沒(méi)有競(jìng)爭(zhēng)到鎖,則內(nèi)核線程會(huì)調(diào)用park()函數(shù)將自己阻塞,這樣CPU就不再分配時(shí)間片給它。

阻塞線程獲取到鎖之后取消阻塞,并且對(duì)應(yīng)的內(nèi)核線程恢復(fù)分配時(shí)間片

持有鎖的線程在釋放鎖之后,從_EntryList中取出一個(gè)線程時(shí),就會(huì)調(diào)用unpark()函數(shù),取消對(duì)應(yīng)內(nèi)核線程的阻塞狀態(tài),這樣它才能有機(jī)會(huì)去競(jìng)爭(zhēng)monitor鎖

ObjectMonitor Enter方法總結(jié):

  1. ObjectMonitor 內(nèi)部通過(guò)一個(gè) CXQ 隊(duì)列保存所有的等待線程
  2. 在實(shí)際進(jìn)入隊(duì)列之前,會(huì)反復(fù)嘗試 lock,在某些系統(tǒng)上會(huì)存在 CPU 親和力的優(yōu)化
  3. 入隊(duì)的時(shí)候,通過(guò) ObjectWaiter 對(duì)象將當(dāng)前線程包裹起來(lái),并且入到 CXQ 隊(duì)列的頭部
  4. 入隊(duì)成功以后,會(huì)根據(jù)當(dāng)前線程是否為第一個(gè)等待線程做不同的處理
  5. 如果是第一個(gè)等待線程,會(huì)根據(jù)一個(gè)簡(jiǎn)單的「退避算法」來(lái)有條件的 wait
  6. 如果不是第一個(gè)等待線程,那么會(huì)執(zhí)行無(wú)限期等待
  7. 線程的 park 在 posix 系統(tǒng)上是通過(guò) pthread_cond_wait() 實(shí)現(xiàn)的
  8. 當(dāng)一個(gè)線程獲得對(duì)象鎖成功之后,就可以執(zhí)行自定義的同步代碼塊了

ObjectMonitor exit方法

當(dāng)前線程執(zhí)行完代碼塊以后,會(huì)進(jìn)入到ObjectMonitor exit方法,釋放當(dāng)前對(duì)象鎖,方便下一個(gè)線程來(lái)獲取這個(gè)鎖,下面我們逐步分析下 exit 的實(shí)現(xiàn)過(guò)程。

exit函數(shù)方法較長(zhǎng),但是整體上的結(jié)構(gòu)比較清晰。

void ObjectMonitor::exit(bool not_suspended, TRAPS) {
 ?for (;;) {
 ? ?//...
 ? ?ObjectWaiter * w = NULL;
 ? ?int QMode = Knob_QMode;
 ? ?if (QMode == 2 && _cxq != NULL) {
 ? ? ?//
 ?  }
?
 ? ?if (QMode == 3 && _cxq != NULL) {
 ? ? ?//
 ?  }
?
 ? ?if (QMode == 4 && _cxq != NULL) {
 ? ? ?//
 ?  }
?
 ? ?// ...
?
 ? ?if (QMode == 1) {
 ? ? ?//
 ?  } else {
 ? ? ?// QMode == 0 or QMode == 2 
 ?  }
 ? ?// ...
  }
}
?
復(fù)制代碼

上面的 exit 函數(shù)整體上分為如下幾個(gè)部分:

  1. 根據(jù) Knob_QMode 的值和 _cxq 是否為空?qǐng)?zhí)行不同策略
  2. 根據(jù)一定策略喚醒等待隊(duì)列中的下一個(gè)線程

其中Knob_QMode這個(gè)變量主要用來(lái)指定在 exit 的時(shí)候 EntryList 和 CXQ 隊(duì)列之間的喚醒關(guān)系,也就是說(shuō),當(dāng) EntryList 和 CXQ 中都有等待的線程時(shí),因?yàn)?exit 之后只能有一個(gè)線程得到鎖,這個(gè)時(shí)候選擇喚醒哪個(gè)隊(duì)列中的線程是一個(gè)值得考慮的事情。JVM默認(rèn)值為0,我暫時(shí)沒(méi)有找到可以修改Knob_QMode的方法,除了重新編譯JVM源代碼,所以,這里我們暫時(shí)只討論Knob_QMode=0的情況。代碼如下:

void ObjectMonitor::exit(bool not_suspended, TRAPS) {
 ?  ...
 ?for(;;) {
 ? ?ObjectWaiter * w = NULL ;
 ? ?w = _EntryList  ;
 ? ?if (w != NULL) {
 ? ? ?assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
 ? ? ?ExitEpilog (Self, w) ;
 ? ? ?return ;
 ?  }
 ? ?w = _cxq ;
 ? ?if (w == NULL) continue ;
 ? ?for (;;) {
 ? ? ?assert (w != NULL, "Invariant") ;
 ? ? ?ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
 ? ? ?if (u == w) break ;
 ? ? ?w = u ;
 ?  }
 ? ?assert (w != NULL ? ? ? ? ? ?  , "invariant") ;
 ? ?assert (_EntryList ?== NULL ?  , "invariant") ;
 ? ?// 抽離出來(lái)的QMode == 0 or QMode == 2情況下代碼;
 ? ?_EntryList = w ;
 ? ?ObjectWaiter * q = NULL ;
 ? ?ObjectWaiter * p ;
 ? ?// 將單向鏈表構(gòu)造成雙向環(huán)形鏈表;
 ? ?for (p = w ; p != NULL ; p = p->_next) {
 ? ? ?guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
 ? ? ?p->TState = ObjectWaiter::TS_ENTER ;
 ? ? ?p->_prev = q ;
 ? ? ?q = p ;
 ?  }
 ? ?// _succ表示已經(jīng)存在喚醒的線程;
 ? ?if (_succ != NULL) continue;
 ? ?w = _EntryList  ;
 ? ?if (w != NULL) {
 ? ? ?guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
 ? ? ?ExitEpilog (Self, w) ;
 ? ? ?return ;
 ?  }
  }
}
復(fù)制代碼
  1. 若EntryList隊(duì)列的頭節(jié)點(diǎn)_EntryList不為null,那么直接喚醒該頭節(jié)點(diǎn)封裝的線程,然后返回;
  2. 1的條件不滿(mǎn)足,程序繼續(xù)向下執(zhí)行,若cxq隊(duì)列的頭節(jié)點(diǎn)_cxq為null,則跳過(guò)當(dāng)次循環(huán);
  3. 若程序繼續(xù)向下執(zhí)行說(shuō)明cxq隊(duì)列不為空,EntryList隊(duì)列為空。接下來(lái)是一個(gè)內(nèi)嵌的for循環(huán),目的是取出cxq隊(duì)列中的所有元素,方法是通過(guò)一個(gè)臨時(shí)變量指針獲得構(gòu)成隊(duì)列的整個(gè)鏈表,然后將_cxq指針置為NULL;
  4. 第二個(gè)內(nèi)嵌for循環(huán)是QMode == 0策略的內(nèi)容,目的在于將第三步得到的單向鏈表傾倒(drain)進(jìn)EntryList隊(duì)列,具體方法是將_EntryList指針指向單向鏈表的頭節(jié)點(diǎn),然后通過(guò)for循環(huán)將單向鏈表構(gòu)造成雙向環(huán)形鏈表;
  5. 通過(guò)ExitEpilog函數(shù)釋放monitor鎖并喚醒EntryList隊(duì)列的頭節(jié)點(diǎn);

鎖優(yōu)化

鎖的類(lèi)型

對(duì)于一個(gè)synchronized鎖,如果它只被一個(gè)線程使用,那么,synchronzied鎖底層使用偏向鎖來(lái)實(shí)現(xiàn)。如果它被多個(gè)線程交叉使用(你用完我再用),不存在競(jìng)爭(zhēng)使用的情況,那么,synchronized鎖底層使用輕量級(jí)鎖來(lái)實(shí)現(xiàn)。如果它存在被競(jìng)爭(zhēng)使用的情況,那么,synchronized鎖底層使用重量級(jí)鎖來(lái)實(shí)現(xiàn)。

上面再講到重量級(jí)鎖需要用到對(duì)象頭的Mark Word,實(shí)際上,偏向鎖和輕量級(jí)鎖也要用到Mark Word。

無(wú)鎖在Mark Word中的記錄有unused(25bits)、hashCode(31bits)、cms_free、GC age、偏向1、鎖標(biāo)志位01

偏向鎖

偏向鎖在Mark Word中的記錄有,threadId(51bits)、epoch(2bits)、cms_free(1bit)、GC age(4bits)、偏向1,鎖標(biāo)志位01

如果我們?cè)O(shè)置了JVM參數(shù)-XX:BiasedLockingStartupDelay=0,那么,Mark Word會(huì)在對(duì)象創(chuàng)建之后,直接進(jìn)入偏向鎖狀態(tài)。

java線程-synchronized詳解

如上圖所示:

  1. 當(dāng)一個(gè)對(duì)象被創(chuàng)建出來(lái),還沒(méi)有持有偏向鎖,此時(shí)Mark Word字段中的threadID為0,當(dāng)前線程會(huì)使用CPU提供的CAS原子操作來(lái)競(jìng)爭(zhēng)這個(gè)偏向鎖。
  2. 當(dāng)threadID并未指向當(dāng)前線程,則通過(guò)CAS操作競(jìng)爭(zhēng)鎖。如果競(jìng)爭(zhēng)成功,則將Mark Word中threadID設(shè)置為當(dāng)前線程ID
  3. 如果CAS獲取偏向鎖失敗,則表示有競(jìng)爭(zhēng)。當(dāng)?shù)竭_(dá)全局安全點(diǎn)(safepoint)時(shí)獲得偏向鎖的線程被掛起,偏向鎖升級(jí)為輕量級(jí)鎖,然后被阻塞在安全點(diǎn)的線程繼續(xù)往下執(zhí)行同步代碼。
  4. 如果CAS已經(jīng)成功獲取到偏向鎖,那就開(kāi)始執(zhí)行代碼,如果執(zhí)行完代碼,線程也不會(huì)立刻解鎖偏向鎖,也就是不會(huì)更改threadID為0。這是偏向鎖有別于輕量級(jí)鎖和重量級(jí)鎖。這樣做的目的是提高加鎖的效率,當(dāng)同一個(gè)線程再次請(qǐng)求這個(gè)偏向鎖的時(shí)候,線程會(huì)查看Mark Word,發(fā)現(xiàn)還是處于偏向鎖狀態(tài),并且threadID就是自己的threadID,線程不再需要做任何加鎖操作,就可以直接執(zhí)行業(yè)務(wù)代碼。
  5. 偏向鎖不會(huì)主動(dòng)解鎖,當(dāng)其他線程嘗試獲取偏向鎖時(shí),持有偏向鎖的線程才會(huì)釋放鎖,JVM需要暫停持有偏向鎖的線程,然后查看它是否還在使用這個(gè)偏向鎖,如果線程不再使用這個(gè)偏向鎖,那么jvm就會(huì)將Mark Word設(shè)置為無(wú)鎖狀態(tài)。如果線程還在使用這個(gè)偏向鎖,那么虛擬機(jī)就將偏向鎖升級(jí)為輕量級(jí)鎖。

jvm需要根據(jù)持有偏向鎖的線程是否正在使用偏向鎖,來(lái)決定將鎖升級(jí)為無(wú)鎖還是偏向鎖,這是一個(gè)CAS的復(fù)合操作,存在線程安全問(wèn)題,但又無(wú)法使用CPU提供的CAS指令來(lái)實(shí)現(xiàn),所以解決方案就是jvm會(huì)復(fù)用垃圾回收器中的STW功能,來(lái)停止持有偏向鎖的線程。

輕量級(jí)鎖和自旋鎖

當(dāng)一個(gè)線程去競(jìng)爭(zhēng)鎖時(shí),它會(huì)先檢查Mark Word的的鎖標(biāo)志位,如果鎖標(biāo)志位是01并且相鄰偏向位為0(無(wú)鎖狀態(tài))或鎖標(biāo)志位是00(輕量級(jí)鎖狀態(tài)),那么,這就說(shuō)明鎖已經(jīng)升級(jí)到了輕量級(jí)鎖。

如果是無(wú)鎖狀態(tài),jvm會(huì)將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間,用于存儲(chǔ)鎖對(duì)象目前的Mark Word的拷貝(官方稱(chēng)之為Displaced Mark Word),其目的主要是為了輕量級(jí)鎖解鎖時(shí)快速恢復(fù)到無(wú)鎖狀態(tài)。

拷貝成功后,JVM將使用CAS操作嘗試將Mark Word中的Lock Record指針更新為指向自己的Lock Record。

如果更新成功,那么這個(gè)線程就擁有了該對(duì)象的鎖,并將對(duì)象的Mark Word的標(biāo)志位設(shè)置為“00”,表示此對(duì)象已經(jīng)是輕量級(jí)鎖狀態(tài)。

如果更新失敗,按理來(lái)說(shuō)應(yīng)該要升級(jí)成重量級(jí)鎖,但是JVM對(duì)此做了優(yōu)化,JVM首先會(huì)檢查對(duì)象的Mark Word是否指向當(dāng)前線程的棧幀,如果是就說(shuō)明當(dāng)前線程已經(jīng)擁有了這個(gè)對(duì)象的鎖,那就可以直接進(jìn)入同步塊繼續(xù)執(zhí)行,否則說(shuō)明多個(gè)線程競(jìng)爭(zhēng)鎖。若當(dāng)前只有一個(gè)等待線程,則該線程通過(guò)自旋進(jìn)行等待。但是當(dāng)自旋超過(guò)一定的次數(shù)(默認(rèn)是10次,可以使用-XX:PreBlockSpin來(lái)更改),或者一個(gè)線程在持有鎖,一個(gè)在自旋,又有第三個(gè)來(lái)訪時(shí),輕量級(jí)鎖升級(jí)為重量級(jí)鎖。

這里有個(gè)問(wèn)題,自旋多少次合適?如果自旋次數(shù)太少,有可能剛升級(jí)為重量級(jí)鎖,另一個(gè)線程就釋放了輕量級(jí)鎖,這樣就很可惜。如果自旋次數(shù)很多,CPU就會(huì)做了很多無(wú)用功。針對(duì)這個(gè)問(wèn)題,JVM發(fā)明了自適應(yīng)自旋鎖。

自旋鎖在JDK1.4.2中引入,使用-XX:+UseSpinning來(lái)開(kāi)啟。JDK 6中變?yōu)槟J(rèn)開(kāi)啟,并且引入了自適應(yīng)的自旋鎖)。如果上次自旋之后成功等到了另一個(gè)線程釋放輕量級(jí)鎖,那么下次自旋的次數(shù)就增加,如果上次自旋沒(méi)有等到等到另一個(gè)線程釋放輕量級(jí)鎖,那么下次自旋的次數(shù)就減少。

如果線程自旋等待輕量級(jí)鎖失敗,只能將輕量級(jí)線程升級(jí)為重量級(jí)線程。跟偏向鎖升級(jí)不同的是,輕量級(jí)鎖升級(jí)不需要STW,因?yàn)樗械腃AS操作都是由硬件提供的原子CAS指令來(lái)完成的。

重量級(jí)鎖

升級(jí)為重量級(jí)鎖時(shí),鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”,此時(shí)Mark Word中存儲(chǔ)的是指向重量級(jí)鎖的指針,此時(shí)等待鎖的線程都會(huì)進(jìn)入阻塞狀態(tài)。這個(gè)就是創(chuàng)建Monitor鎖的過(guò)程

總結(jié)

綜上,偏向鎖通過(guò)對(duì)比Mark Word解決加鎖問(wèn)題,避免執(zhí)行CAS操作。而輕量級(jí)鎖是通過(guò)用CAS操作和自旋來(lái)解決加鎖問(wèn)題,避免線程阻塞和喚醒而影響性能。重量級(jí)鎖是將除了擁有鎖的線程以外的線程都阻塞。

java線程-synchronized詳解

鎖消除

JVM在執(zhí)行JIT編譯的時(shí)候,會(huì)根據(jù)對(duì)代碼的逃逸分析,去掉某些沒(méi)有必要的鎖。例如:

public String concat(String s1, String s2){
 ?StringBuffer buffer = new StringBuffer(); 
 ?buffer.append(s1).append(s2);
 ?return buffer.toString();
}
復(fù)制代碼

StringBuffer中的append函數(shù)使用了Sychronized修飾,加了鎖,但是,buffer是局部變量,不會(huì)被多線程共享,更不會(huì)在多線程環(huán)境下調(diào)用它的append()函數(shù),所以append函數(shù)的鎖可以被優(yōu)化消除。

鎖粗化

JVM在執(zhí)行JIT編譯時(shí),可能會(huì)擴(kuò)大鎖的范圍,對(duì)多個(gè)小范圍代碼的加鎖,合并成一個(gè)對(duì)大范圍代碼加鎖的操作叫做鎖粗化。例如:

private StringBuffer buffer = new StringBuffer();
?
public void reproduce(String s){
 ?for(int i = 0; i < 10000; i ++){
 ? ?buffer.append(s);
  }
}
復(fù)制代碼

執(zhí)行10000次append,會(huì)加鎖解鎖10000次,通過(guò)鎖粗化,編譯器將append函數(shù)的鎖去掉,移到for循環(huán)外面,這樣只需要加鎖解鎖一次就可以了。

Synchronized的缺點(diǎn)

  1. 無(wú)法判斷獲取鎖的狀態(tài)。
  2. 雖然會(huì)自動(dòng)釋放鎖,但如果如果鎖的那個(gè)方法執(zhí)行時(shí)間較長(zhǎng)就會(huì)一直占用著不去釋放,不能讓使用同一把鎖的方法繼續(xù)執(zhí)行,影響程序的運(yùn)行。不能設(shè)置超時(shí)。
  3. 當(dāng)多個(gè)線程嘗試獲取鎖時(shí),未獲取到鎖的線程會(huì)不斷的嘗試獲取鎖,而不會(huì)發(fā)生中斷,這樣會(huì)造成性能消耗。
  4. 不能夠?qū)崿F(xiàn)公平鎖

作者:Shawn_Shawn
原文鏈接:https://juejin.cn/post/7195569817940672572
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-494795.html

到了這里,關(guān)于java線程-synchronized詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【JavaEE】多線程之線程安全(synchronized篇),死鎖問(wèn)題

    【JavaEE】多線程之線程安全(synchronized篇),死鎖問(wèn)題

    線程安全問(wèn)題 觀察線程不安全 線程安全問(wèn)題的原因? 從原子性入手解決線程安全問(wèn)題?——synchronized synchronized的使用方法? synchronized的互斥性和可重入性 死鎖 死鎖的三個(gè)典型情況? 死鎖的四個(gè)必要條件? 破除死鎖 在前面的章節(jié)中,我們也了解到多線程為我們的程序帶來(lái)了

    2024年02月01日
    瀏覽(25)
  • Synchronized與Java線程的關(guān)系

    Synchronized與Java線程的關(guān)系

    ? Java多線程處理任務(wù)時(shí),為了線程安全,通常會(huì)對(duì)共享資源進(jìn)行加鎖,拿到鎖的線程才能進(jìn)行訪問(wèn)共享資源。而加鎖方式通過(guò)都是Synchronized鎖或者Lock鎖。 ? 那么多線程在協(xié)同工作的時(shí)候,線程狀態(tài)的變化都與鎖對(duì)象有關(guān)系。 ? Java采用synchronized、以互斥同步的方式

    2024年02月11日
    瀏覽(12)
  • 線程的狀態(tài),多線程帶來(lái)的風(fēng)險(xiǎn),synchronized關(guān)鍵字及死鎖問(wèn)題

    線程的狀態(tài),多線程帶來(lái)的風(fēng)險(xiǎn),synchronized關(guān)鍵字及死鎖問(wèn)題

    目錄 狀態(tài)? 線程的意義 多線程帶來(lái)的風(fēng)險(xiǎn)——線程安全? 線程安全的概念 線程不安全的原因 搶占式執(zhí)行,隨機(jī)性調(diào)度 修改共享數(shù)據(jù) 原子性-加?? 可見(jiàn)性 指令重排序 解決線程不安全問(wèn)題(學(xué)完線程再總結(jié)) synchronized——監(jiān)視器鎖monitor lock?編輯? ?互斥 使用示例

    2024年02月06日
    瀏覽(40)
  • Java多線程篇(1)——深入分析synchronized

    Java多線程篇(1)——深入分析synchronized

    synchronized實(shí)現(xiàn)原理的由淺入深依次為 字節(jié)碼層面:monitorenter/monitorexit 指令 java對(duì)象層面: Mark Word 對(duì)象頭 JVM層面: CAS、自旋 、 ObjectMonitor(MESA管層模型:cxq,entryList,wait三個(gè)隊(duì)列) 操作系統(tǒng)層面: mutex 鎖 其中 mark word 對(duì)象頭如下圖: 說(shuō)到鎖升級(jí),我相信很多人都錯(cuò)

    2024年02月09日
    瀏覽(16)
  • 【創(chuàng)作贏紅包】Java多線程:synchronized鎖方法塊

    synchronized同步代碼塊 用synchronized聲明方法在某些情況下是有弊端的,比如A線程調(diào)用同步方法執(zhí)行一個(gè)較長(zhǎng)時(shí)間的任務(wù),那么B線程必須等待比較長(zhǎng)的時(shí)間。這種情況下可以嘗試使用synchronized同步語(yǔ)句塊來(lái)解決問(wèn)題。看一下例子: ??? 運(yùn)行結(jié)果,分兩部分來(lái)看: synchr

    2023年04月09日
    瀏覽(18)
  • Java多線程(4)---死鎖和Synchronized加鎖流程

    Java多線程(4)---死鎖和Synchronized加鎖流程

    目錄 前言 一.synchronized 1.1概念? 1.2Synchronized是什么鎖? 1.3Synchronized加鎖工作過(guò)程 1.4其他優(yōu)化操作 二.死鎖 2.1什么是死鎖 2.2死鎖的幾個(gè)經(jīng)典場(chǎng)景 2.3死鎖產(chǎn)生的條件 2.4如何解決死鎖 ??個(gè)人主頁(yè):tq02的博客_CSDN博客-C語(yǔ)言,Java,Java數(shù)據(jù)結(jié)構(gòu)領(lǐng)域博主 ?? 本文由 tq02 原創(chuàng),首發(fā)于

    2024年02月13日
    瀏覽(16)
  • Java并發(fā)編程(三)線程同步 上[synchronized/volatile]

    Java并發(fā)編程(三)線程同步 上[synchronized/volatile]

    當(dāng)使用多個(gè)線程來(lái)訪問(wèn)同一個(gè)數(shù)據(jù)時(shí),將會(huì)導(dǎo)致數(shù)據(jù)不準(zhǔn)確,相互之間產(chǎn)生沖突,非常容易出現(xiàn)線程安全問(wèn)題,比如多個(gè)線程都在操作同一數(shù)據(jù),都打算修改商品庫(kù)存,這樣就會(huì)導(dǎo)致數(shù)據(jù)不一致的問(wèn)題。 所以我們通過(guò)線程同步機(jī)制來(lái)保證線程安全,加入同步鎖以避免在該線程沒(méi)有完成

    2024年02月13日
    瀏覽(29)
  • java八股文面試[多線程]——synchronized鎖升級(jí)過(guò)程

    java八股文面試[多線程]——synchronized鎖升級(jí)過(guò)程

    速記:偏向-輕量-重量 上面講到鎖有四種狀態(tài),并且會(huì)因?qū)嶋H情況進(jìn)行膨脹升級(jí),其膨脹方向是: 無(wú)鎖——偏向鎖——輕量級(jí)鎖——重量級(jí)鎖 ,并且膨脹方向 不可逆 一.鎖升級(jí)理論. 在synchronized鎖升級(jí)過(guò)程中涉及到以下幾種鎖.先說(shuō)一下這幾種鎖是什么意思. 偏向鎖: 只有一個(gè)

    2024年02月10日
    瀏覽(24)
  • 【并發(fā)多線程】java并發(fā)中的Synchronized關(guān)鍵詞

    如果在多線程的環(huán)境中,我們經(jīng)常會(huì)遇到資源競(jìng)爭(zhēng)的情況,比如多個(gè)線程要去同時(shí)修改同一個(gè)共享變量,這時(shí)候,就需要對(duì)資源的訪問(wèn)方法進(jìn)行一定的處理,保證同一時(shí)間只有一個(gè)線程訪問(wèn)。 java提供了synchronized,方便我們實(shí)現(xiàn)上述操作。 我們舉個(gè)例子,我們創(chuàng)建一個(gè)

    2023年04月13日
    瀏覽(23)
  • 多線程系列(十六) -常用并發(fā)原子類(lèi)詳解

    多線程系列(十六) -常用并發(fā)原子類(lèi)詳解

    在 Java 的 java.util.concurrent 包中,除了提供底層鎖、并發(fā)同步等工具類(lèi)以外,還提供了一組原子操作類(lèi),大多以 Atomic 開(kāi)頭,他們位于 java.util.concurrent.atomic 包下。 所謂原子類(lèi)操作,顧名思義,就是這個(gè)操作要么全部執(zhí)行成功,要么全部執(zhí)行失敗,是保證并發(fā)編程安全的重要一

    2024年03月11日
    瀏覽(21)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包