java重入鎖,再探JAVA重入鎖

5afe4de83188424822e688c5ea4397f1.png

之前的文章中簡單的為大家介紹了重入鎖JAVA并發之多線程基礎(2)。這里面也是簡單的為大家介紹了重入鎖的幾種性質,這里我們就去探索下里面是如何實現的。

我們知道在使用的時候,必須鎖先有定義,然后我們再拿著當前的鎖進行加鎖操作,然后處理業務,最后是釋放鎖的操作(這里就拿里面非公平鎖的實現來講解)。

字節碼操作

public class com.montos.lock.ReentrantLockDemo implements java.lang.Runnable {

public static java.util.concurrent.locks.ReentrantLock lock;

public static int k;

public com.montos.lock.ReentrantLockDemo();

Code:

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

public void run();

Code:

0: iconst_0

1: istore_1

2: iload_1

3: sipush 1000

6: if_icmpge 29 //int類型的值進行棧頂比較

9: getstatic #2 // Field lock:Ljava/util/concurrent/locks/ReentrantLock;

12: invokevirtual #3 // Method java/util/concurrent/locks/ReentrantLock.lock:()V

15: getstatic #4 // Field k:I

18: iconst_1

19: iadd

20: putstatic #4 // Field k:I

23: iinc 1, 1

26: goto 2

29: iconst_0

30: istore_1

31: iload_1

32: sipush 1000

35: if_icmpge 50

38: getstatic #2 // Field lock:Ljava/util/concurrent/locks/ReentrantLock;

41: invokevirtual #5 // Method java/util/concurrent/locks/ReentrantLock.unlock:()V

44: iinc 1, 1

47: goto 31

50: return

public static void main(java.lang.String[]) throws java.lang.InterruptedException;

Code:

0: new #6 // class com/montos/lock/ReentrantLockDemo

3: dup

4: invokespecial #7 // Method "":()V

7: astore_1

8: new #8 // class java/lang/Thread

11: dup

12: aload_1

13: invokespecial #9 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V

16: astore_2

17: new #8 // class java/lang/Thread

20: dup

21: aload_1

22: invokespecial #9 // Method java/lang/Thread."":(Ljava/lang/Runnable;)V

25: astore_3

26: aload_2

27: invokevirtual #10 // Method java/lang/Thread.start:()V

30: aload_3

31: invokevirtual #10 // Method java/lang/Thread.start:()V

34: aload_2

35: invokevirtual #11 // Method java/lang/Thread.join:()V

38: aload_3

39: invokevirtual #11 // Method java/lang/Thread.join:()V

42: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;

45: getstatic #4 // Field k:I

48: invokevirtual #13 // Method java/io/PrintStream.println:(I)V

51: return

static {};

Code:

0: new #14 // class java/util/concurrent/locks/ReentrantLock

3: dup

4: invokespecial #15 // Method java/util/concurrent/locks/ReentrantLock."":()V

7: putstatic #2 // Field lock:Ljava/util/concurrent/locks/ReentrantLock;

10: iconst_0

11: putstatic #4 // Field k:I

14: return

}

復制代碼

這里面無非就是入棧,棧元素比較,出棧放入變量中這些操作,沒有之前的synchronized里面的監視器相關指令限制,只是簡單的一些棧操作。

加鎖操作

final void lock(){

if (compareAndSetState(0, 1)) //將同步狀態從0變成1 采用cas進行更新

setExclusiveOwnerThread(Thread.currentThread());//設置當前擁有獨占訪問權的線程。

else

acquire(1);//沒有獲取到鎖,則進行嘗試操作

}

復制代碼

往下面的選擇走:

public final void acquire(int arg){

//先進行再次嘗試獲取鎖的操作,如果獲取失敗則將當前加入隊列中,并設置中斷標志。

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

復制代碼

首先走嘗試獲取鎖的操作(這里還是走非公平鎖的):

final boolean nonfairTryAcquire(int acquires){

final Thread current = Thread.currentThread();//拿到當前線程

int c = getState();//同步狀態

if (c == 0) {//再次做獲取鎖的操作

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

else if (current == getExclusiveOwnerThread()) {//是否是當前線程已經占有

int nextc = c + acquires;//原本的狀態數值+當前傳入數值

if (nextc < 0) // overflow

throw new Error("Maximum lock count exceeded");

setState(nextc);//設置新的狀態

return true;

}

return false;

}

復制代碼

接著往下走:

private Node addWaiter(Node mode){

//獨占模式進行封裝當前線程

Node node = new Node(Thread.currentThread(), mode);

Node pred = tail;

if (pred != null) {如果尾節點不為null,將當前的節點接入并返回

node.prev = pred;

if (compareAndSetTail(pred, node)) {

pred.next = node;

return node;

}

}

enq(node);

return node;

}

復制代碼

繼續往下走:

private Node enq(final Node node){

for (;;) {//

Node t = tail;

if (t == null) { // 初始化尾節點

if (compareAndSetHead(new Node()))

tail = head;

} else {

node.prev = t;

if (compareAndSetTail(t, node)) {//尾節點與當前的節點互換

t.next = node;

return t;//返回當前節點

}

}

}

}

復制代碼

接著回去往下走:

final boolean acquireQueued(final Node node, int arg){

boolean failed = true;

try {

boolean interrupted = false;

for (;;) {

final Node p = node.predecessor();

if (p == head && tryAcquire(arg)) {//如果當前節點前一個節點是頭節點,并嘗試獲鎖成功

setHead(node);//設置當前的頭結點

p.next = null; // 手動清除引用 幫助GC

failed = false;

return interrupted;

}

//檢測獲取鎖失敗的節點狀態 以及暫時掛起并返回當前的中斷標志

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} finally {

if (failed)

cancelAcquire(node);//取消正在進行的獲取嘗試。

}

}

復制代碼

說真的,咱們直接看失敗的情況,我們接著往下走:

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node){

//檢查和更新無法獲取的節點的狀態。

int ws = pred.waitStatus;

if (ws == Node.SIGNAL)

//該節點已經設置了請求釋放信號狀態,所以可以進行安全掛起

return true;

if (ws > 0) {

do {//清除不需要執行的節點

node.prev = pred = pred.prev;

} while (pred.waitStatus > 0);

pred.next = node;

} else {

//waitstatus必須為0或傳播。表明我們需要信號,但不要掛起。調用者重試以確保在掛起前無法獲取。

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

}

return false;

}

復制代碼

然后看向下一個方法:

private final boolean parkAndCheckInterrupt(){

LockSupport.park(this);//掛起當前線程

return Thread.interrupted();//返回中斷標識

}

復制代碼

上面的取消獲取隊列里面的節點就不看了..cancelAcquire(node),里面就是取消正在進行的獲取嘗試。同時將無需的節點移除。當上面的操作走完之后就設置當前線程中斷標識。這里面主要流程是說如果加鎖不成功之后,對于當前線程是怎么執行操作的,我們可以看到,里面的方法中大部分在獲取不到鎖之后,下一步操作中會再次嘗試獲取下,如果獲取不到才會繼續執行,獲取到了我們就可以直接使用,這里也是多線程操作里面的魅力,每一個空隙中就可能會讓當前線程進行獲得鎖的操作。

釋放鎖操作

釋放鎖的步驟就簡單許多了:

public final boolean release(int arg){

if (tryRelease(arg)) {//嘗試釋放鎖

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);//喚醒節點的后續節點

return true;

}

return false;

}

復制代碼

咱們繼續往下看:

protected final boolean tryRelease(int releases){

int c = getState() - releases;//同步狀態-當前釋放狀態值

if (Thread.currentThread() != getExclusiveOwnerThread())//如果當前線程不是拿鎖線程,則報監視器相關錯誤

throw new IllegalMonitorStateException();

boolean free = false;

if (c == 0) {

free = true;//只有當前重入次數為0,才能返回true

setExclusiveOwnerThread(null);//當前獨占線程設為NULL

}

setState(c);//重新設置同步狀態

return free;

}

復制代碼

然后往下走:

private void unparkSuccessor(Node node){

//當前狀態為負數,則嘗試清除當前的線程狀態

int ws = node.waitStatus;

if (ws < 0)

compareAndSetWaitStatus(node, ws, 0);

//清除取消或無效的節點,從尾部向后移動以找到實際節點

Node s = node.next;

if (s == null || s.waitStatus > 0) {

s = null;

for (Node t = tail; t != null && t != node; t = t.prev)

if (t.waitStatus <= 0)

s = t;

}

if (s != null)

LockSupport.unpark(s.thread);//釋放當前線程

}

復制代碼從上面的順序往下面來看,我們主要發現線程在拿鎖階段是有許多的操作的,要根據線程的狀態再將線程從等待隊列中移除。釋放的時候就顯得簡潔了許多,我們只需要看到當前線程的狀態-1,然后看看是否是重入的。

我們通過一個簡單的重入鎖代碼可以看到,作者在用無鎖的操作去獲得鎖,這個整體的步驟里面考慮的東西很多,每一個時刻,線程都有可能千變萬化,我們需要了解的是我們每一個步驟都需要可能發生的情況。如果能夠考慮到發生的情況,那么有些步驟就可以直接跳過,我們直接就可以獲得最后的結果(這塊在線程嘗試獲鎖的階段可以體現)。有小伙伴對于重入鎖還有什么看法的可以在下面進行留言,我們可以相互學習,共同進步~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/393198.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/393198.shtml
英文地址,請注明出處:http://en.pswp.cn/news/393198.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

azure服務器_如何使用Azure Functions和SendGrid構建無服務器報表服務器

azure服務器It’s 2018 and I just wrote a title that contains the words “Serverless server”. Life has no meaning.那是2018年&#xff0c;我剛剛寫了一個標題&#xff0c;其中包含“無服務器服務器”一詞。 生活沒有意義。 Despite that utterly contradictory headli…

【GoWeb開發實戰】Cookie

cookie Web開發中一個很重要的議題就是如何做好用戶的整個瀏覽過程的控制&#xff0c;因為HTTP協議是無狀態的&#xff0c;所以用戶的每一次請求都是無狀態的&#xff0c;我們不知道在整個Web操作過程中哪些連接與該用戶有關&#xff0c;我們應該如何來解決這個問題呢&#xff…

PhotoKit 照片庫的管理-獲取圖像

PHAsset部分屬性解析 1、HDR 和全景照片 mediaSubtypes 屬性驗證資源庫中的圖像在捕捉時是否開啟了 HDR&#xff0c;拍攝時是否使用了相機應用的全景模式。 2、收藏和隱藏資源 要驗證一個資源是否被用戶標記為收藏或被隱藏&#xff0c;只要檢查 PHAsset 實例的 favorite 和 hid…

cmail服務器安裝后無法登錄的解決辦法

安裝cmailserver 5.4.6軟件安裝、注冊都非常順利&#xff0c;webmail頁面也都正常打開&#xff0c;但是一點“登錄”就提示錯誤&#xff1a; Microsoft VBScript 運行時錯誤 錯誤 800a01ad ActiveX 部件不能創建對象: CMailCOM.POP3.1 /mail/login.asp&#xff0c;行 42 點“…

matlab對人工智能,MATLAB與人工智能深度學習和機器學習.PDF

MATLAB與人工智能深度學習和機器學習MATLAB 與人工智能&#xff1a;深度學習有多遠&#xff1f;© 2017 The MathWorks, Inc.1機器學習8機器學習無處不在? 圖像識別 [TBD]? 語音識別? 股票預測? 醫療診斷? 數據分析? 機器人? 更多……9什么是機器學習&#xff1f;機…

leetcode1471. 數組中的 k 個最強值(排序)

給你一個整數數組 arr 和一個整數 k 。 設 m 為數組的中位數&#xff0c;只要滿足下述兩個前提之一&#xff0c;就可以判定 arr[i] 的值比 arr[j] 的值更強&#xff1a; |arr[i] - m| > |arr[j] - m| |arr[i] - m| |arr[j] - m|&#xff0c;且 arr[i] > arr[j] 請返回…

Spring中WebApplicationInitializer的理解

現在JavaConfig配置方式在逐步取代xml配置方式。而WebApplicationInitializer可以看做是Web.xml的替代&#xff0c;它是一個接口。通過實現WebApplicationInitializer&#xff0c;在其中可以添加servlet&#xff0c;listener等&#xff0c;在加載Web項目的時候會加載這個接口實…

使用fetch封裝請求_關于如何使用Fetch API執行HTTP請求的實用ES6指南

使用fetch封裝請求In this guide, I’ll show you how to use the Fetch API (ES6) to perform HTTP requests to an REST API with some practical examples you’ll most likely encounter.在本指南中&#xff0c;我將向您展示如何使用Fetch API(ES6 )來執行對REST API的 HTT…

hadoop集群中客戶端修改、刪除文件失敗

這是因為hadoop集群在啟動時自動進入安全模式 查看安全模式狀態&#xff1a;hadoop fs –safemode get 進入安全模式狀態&#xff1a;hadoop fs –safemode enter 退出安全模式狀態&#xff1a;hadoop fs –safemode leave轉載于:https://www.cnblogs.com/lishengnan/p/a123.ht…

OpenStack nova-network 支持多vlan技術實現片段代碼

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748

Rest API

什么是接口測試 接口測試又稱 API 測試 Application Programming Interface 接口測試是測試系統組件間接口的一種測試。重點關注數據傳遞。 接口測試一般會用于多系統間交互開發&#xff0c;或者擁有多個子系統的應用系統開發的測試。 為什么要做接口測試 很多系統關聯都是基于…

php循環checkbox,php循環刪除checkbox | 學步園

一、首先要了解sql語句$SQLdelete from user where id in (1,2,4);表單大概是&#xff1a;form action methodpost input nameID_Dele[] typecheckbox idID_Dele[] value1input nameID_Dele[] typecheckbox idID_Dele[] value2input nameID_Dele[] type首先要了解sql語句$SQL&q…

leetcode1451. 重新排列句子中的單詞(排序)

「句子」是一個用空格分隔單詞的字符串。給你一個滿足下述格式的句子 text : 句子的首字母大寫 text 中的每個單詞都用單個空格分隔。 請你重新排列 text 中的單詞&#xff0c;使所有單詞按其長度的升序排列。如果兩個單詞的長度相同&#xff0c;則保留其在原句子中的相對順序…

Java+Oracle實現事務——JDBC事務

J2EE支持JDBC事務、JTA事務和容器事務事務&#xff0c;這里說一下怎樣實現JDBC事務。 JDBC事務是由Connection對象所控制的&#xff0c;它提供了兩種事務模式&#xff1a;自己主動提交和手動提交&#xff0c;默認是自己主動提交。 自己主動提交就是&#xff1a;在JDBC中。在一個…

開源項目貢獻者_我如何從一名貢獻者轉變為一個開源項目維護者

開源項目貢獻者by Dhanraj Acharya通過Dhanraj Acharya 我如何從一名貢獻者轉變為一個開源項目維護者 (How I went from being a contributor to an Open Source project maintainer) I was a lone software developer. When I was in college, I attended the KDE conference…

網絡攝像頭CVE

CVE-2018-9995 rtsp未授權訪問 rtsp后綴整理&#xff1a; Axis&#xff08;安訊士&#xff09; rtsp:// 192.168.200.202/axis-media/media.amp?videocodech264&resolution1280x720 rtsp://IP地址/mpeg4/media.amp rtsp://IP地址/安迅士/AXIS-media/media.amp123D-Link …

Centos中不從skel目錄里向其中復制任何文件錯誤的解決方法

[rootlocalhost www]# useradd -d /webserver/www/ ithovcom useradd&#xff1a;警告&#xff1a;此主目錄已經存在。 不從 skel 目錄里向其中復制任何文件。 [rootlocalhost www]# ls -a .&http://www.aliyun.com/zixun/aggregation/37954.html">nbsp; .. 發現沒…

leetcode91. 解碼方法

一條包含字母 A-Z 的消息通過以下方式進行了編碼&#xff1a; ‘A’ -> 1 ‘B’ -> 2 … ‘Z’ -> 26 給定一個只包含數字的非空字符串&#xff0c;請計算解碼方法的總數。 示例 1: 輸入: “12” 輸出: 2 解釋: 它可以解碼為 “AB”&#xff08;1 2&#xff09;或者…

php 系統平均負載,Linux_解析Linux系統的平均負載概念,一、什么是系統平均負載(Load a - phpStudy...

解析Linux系統的平均負載概念一、什么是系統平均負載(Load average)&#xff1f;在Linux系統中&#xff0c;uptime、w、top等命令都會有系統平均負載load average的輸出&#xff0c;那么什么是系統平均負載呢&#xff1f;系統平均負載被定義為在特定時間間隔內運行隊列中的平均…

Elastic-job使用及原理

一、原理 elastic-job有lite版和cloud版&#xff0c;最大的區別是有無調度中心&#xff0c;筆者采用的是lite版本&#xff0c;無中心化。 tips: 第一臺服務器上線觸發主服務器選舉。主服務器一旦下線&#xff0c;則重新觸發選舉&#xff0c;選舉過程中阻塞&#xff0c;只有主服…