溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

synchronized和ReentrantLock的基本原理是什么

發(fā)布時間:2021-10-18 11:21:01 來源:億速云 閱讀:166 作者:iii 欄目:編程語言

這篇文章主要介紹“synchronized和ReentrantLock的基本原理是什么”,在日常操作中,相信很多人在synchronized和ReentrantLock的基本原理是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”synchronized和ReentrantLock的基本原理是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

 一、java鎖的類型

java的鎖有這么幾類。

樂觀鎖和悲觀鎖

  • 樂觀鎖就是JVM認為不通過加鎖也能保證并發(fā)的正確性。典型實現是諸如AtomicInteger的實現。

  • 悲觀鎖就是需要加鎖互斥。典型實現是Synchronized(Synchronized屬于樂觀鎖還是悲觀鎖其實跟具體實現有關,大部分場景下都是悲觀鎖)和ReentrantLock。

可重入和不可重入

  • 可重入是指當一個線程獲取了鎖,但是沒有釋放,這個線程又要獲取這個鎖,仍然能獲取成功。Synchronized和ReentrantLock都是可重入鎖。

  • 不可重入是可重入的否命題,這樣自己會把自己死鎖。應該沒有這樣的實現。

公平鎖和非公平鎖

  • 公平鎖是先請求鎖的線程肯定先獲得鎖,也就是FIFO。公平說是不是就是合理的?可能也不一定,因為這會造成上下文的切換。ReentrantLock默認是非公平鎖,但是可以通過構造方法構造公平鎖實例。

  • 非公平鎖是新來的線程有優(yōu)先獲得鎖的機會,也就是可以插隊。合理嗎?可能也不合理,因為這可能造成“餓死”現象:在排隊的舊的線程總是獲取不到鎖。Sysnchronized其實就是非公平鎖。

排他鎖和共享鎖

  • 排他鎖是一個線程獲得鎖之后,其他線程不能再獲得鎖。大多數場景下都是排他鎖。

  • 共享鎖是指多個線程可以同時獲得鎖。常見的是多個線程可以同時獲得讀鎖。

二、synchronized

synchronized基本原理是通過CPU指令實現的。在jdk1.6之前是很重的鎖。因為java的多線程與操作系統(tǒng)的線程是一一對應的。當java線程阻塞的時候需要切換到內核態(tài)的線程進行阻塞,喚醒的時候又要從內核態(tài)切換到用戶態(tài),進行了很重的上下文切換。那么能不能當一個線程獲取不到鎖的時候不阻塞呢?自旋可以嗎?這樣就有了synchronized的四種實現:無鎖、偏向鎖、輕量鎖、重量鎖。

synchronized鎖的是java的對象頭,再詳細點是mark word。

synchronized和ReentrantLock的基本原理是什么

無鎖

這個沒有什么好說的。沒有將這個對象通過synchronized包括。

偏向鎖

當只有一個線程在訪問鎖的時候,會在mark  word中通過CAS的方式設置當前線程的threadId。如果成功的話,加鎖成功(由于只有一個線程,肯定成功)。這樣當這個線程再次請求鎖的時候,看mark  word的thread  id和自己是否相同,如果相同加鎖成功。注意,它是沒有解鎖操作的。如果是另一個線程也來了,由于上一個線程沒有解鎖操作,這個新線程的CAS肯定失敗。這時當JVM沒有字節(jié)碼要執(zhí)行的時候(全局安全點),會檢查上一個線程有沒有結束,如果結束,則通過CAS將mark  word中的thread id字段更新為新線程的threadId。如果上一個線程沒有結束,這就存在并發(fā)了。偏向鎖無法完成使命,需要升級為輕量鎖。

輕量鎖

接著上面偏向鎖的上一個線程A和新的線程B的例子。JVM此時進行一下線程A對mark word的操作。將mark  word拷貝到當前線程的棧空間中,CAS操作mark word的指針指向這個棧空間的地址,CAS操作當前線程的棧空間再加一個指向mark  word的指針,這兩個操作成功后,其實第一個CAS成功就是成功,這樣線程A就獲得了鎖,升級成為了輕量鎖。線程B會自旋等待線程A的釋放。線程A怎么釋放鎖呢?只要將第一個CAS操作的指針(mark  word指向線程棧的指針)釋放了就可以了,線程B自旋檢測mark  word的指向,去搶占鎖。如果此時又來一個線程C呢?是不是也自旋?可以同時有幾個線程自旋?線程B能自旋多少次?這些都是有JVM參數可配置的。

synchronized和ReentrantLock的基本原理是什么

重量鎖

這個其實也沒什么好說的。存在并發(fā)訪問時,直接將線程切換到內核態(tài)阻塞。

三、ReentrantLock

ReentrantLock是通過AQS(AbstractQueuedSynchronizer)實現的。需要解決的問題:

需要有個狀態(tài)表示這個lock對象是不是被搶占了,如果可重入的話,被這個線程搶占了多少次。這個狀態(tài)標識其實就是AQS的state成員變量。對state的操作肯定要線程安全。可以通過CAS解決。

protected final boolean tryAcquire(int acquires) {     final Thread current = Thread.currentThread();     int c = getState();     if (c == 0) {         // 這個是公平鎖的實現。需要判斷隊列中有沒有等待的線程,         // 如果沒有才進行CAS搶占             if (!hasQueuedPredecessors() &&             compareAndSetState(0, acquires)) {             setExclusiveOwnerThread(current);             return true;         }     }     // 這里就是可重入邏輯     else if (current == getExclusiveOwnerThread()) {         int nextc = c + acquires;         if (nextc < 0)             throw new Error("Maximum lock count exceeded");         setState(nextc);         return true;     }     return false; }

多個線程同時搶占lock,只有一個線程能成功,其他線程怎么排隊呢?排隊的線程怎么搶占鎖呢?這就用到了一個隊列。這個隊列的插入是通過自旋和CAS實現的。

private Node addWaiter(Node mode) {     Node node = new Node(mode);     // 循環(huán)嘗試     for (;;) {         Node oldTail = tail;         if (oldTail != null) {             // 無鎖修改前驅指針             node.setPrevRelaxed(oldTail);             // CAS修改tail             if (compareAndSetTail(oldTail, node)) {                 // 修改后續(xù)指針                 oldTail.next = node;                 return node;             }         } else {             initializeSyncQueue();         }     } }

排隊的線程搶占lock呢?

final boolean acquireQueued(final Node node, int arg) {     boolean interrupted = false;     try {         for (;;) {             final Node p = node.predecessor();             // 如果前驅節(jié)點是頭節(jié)點,并且獲取鎖成功,直接返回。             // 但是大多數情況,可能運氣沒這么好             if (p == head && tryAcquire(arg)) {                 setHead(node);                 p.next = null; // help GC                 return interrupted;             }             // 是否需要阻塞             if (shouldParkAfterFailedAcquire(p, node))                 // 這里阻塞                 interrupted |= parkAndCheckInterrupt();         }     } catch (Throwable t) {         cancelAcquire(node);         if (interrupted)             selfInterrupt();         throw t;     } }

怎么喚醒上面阻塞的線程呢?這就要看下釋放邏輯。

public final boolean release(int arg) {     // 釋放lock     if (tryRelease(arg)) {         Node h = head;         if (h != null && h.waitStatus != 0)             unparkSuccessor(h);  // 喚醒頭結點的后續(xù)節(jié)點。注意頭結點是虛節(jié)點,沒有實在意義         return true;     }     return false; }

到此,關于“synchronized和ReentrantLock的基本原理是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI