自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

ReentrantLock可重入、可打斷、鎖超時實現(xiàn)原理

開發(fā) 前端
ReentrantLock還具備鎖超時的能力,調(diào)用tryLock(long timeout, TimeUnit unit)方法,在給定時間內(nèi)獲取鎖,獲取不到就退出,這也是synchronized沒有的功能。

?概述

前面講解了ReentrantLock加鎖和解鎖的原理實現(xiàn),但是沒有闡述它的可重入、可打斷以及超時獲取鎖失敗的原理,本文就重點講解這三種情況。建議大家先看下這篇文章了解下ReentrantLock加鎖的基本原理,圖解ReentrantLock公平鎖和非公平鎖實現(xiàn)原理。

可重入

可重入是指一個線程如果獲取了鎖,那么它就是鎖的主人,那么它可以再次獲取這把鎖,這種就是理解為重入,簡而言之,可以重復(fù)獲取同一把鎖,不會造成阻塞,舉個例子如下:

@Test
public void testRepeatLock() {
ReentrantLock reentrantLock = new ReentrantLock();
// 第一次獲取鎖
reentrantLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " first get lock");
// 再次獲取鎖
tryAgainLock(reentrantLock);
}finally {
reentrantLock.unlock();
}
}

public void tryAgainLock(ReentrantLock reentrantLock) {
// 第2次獲取鎖
reentrantLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " second get lock");
}finally {
reentrantLock.unlock();
}
}

圖片

  • 同一個線程使用ReentrantLock多次獲取鎖,不會阻塞
  • 申請幾把鎖,最后需要解除幾把鎖

那你知道是怎么實現(xiàn)的嗎?

概述的文章中已經(jīng)講解了ReentrantLock整個的加鎖和解鎖的過程,可重入實現(xiàn)就在其中,這里著重關(guān)注下申請鎖的方法tryAcquire,最終會調(diào)用nonfairTryAcquire方法。

圖片

如果已經(jīng)有線程獲得了鎖, 并且占用鎖的線程是當前線程, 表示【發(fā)生了鎖重入】,上圖的1步驟

計算出沖入的次數(shù)nextc等于當前次數(shù)+新增次數(shù),acquires等于1

更新 state 的值,這里不使用 cas 是因為當前線程正在持有鎖,所以這里的操作相當于在一個管程內(nèi), 然后返回ture,表明再次申請鎖成功。

可打斷

ReentrantLock相比于synchronized加鎖一大優(yōu)勢是可打斷,那么什么是可打斷呢?ReentrantLock通過lockInterruptibly()?加鎖,如果一直獲取不到鎖,可以通過調(diào)用線程的interrupt()提前終止線程。舉個例子:

@Test
public void testInterrupt() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();

// 主線程普通加鎖
System.out.println("主線程優(yōu)先獲取鎖");
lock.lock();
try {
// 創(chuàng)建子線程
Thread t1 = new Thread(() -> {
try {
System.out.println("t1嘗試獲取打斷鎖");
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println("t1沒有獲取到鎖,被打斷,直接返回");
return;
}
try {
System.out.println("t1成功獲取鎖");
} finally {
System.out.println("t1釋放鎖");
lock.unlock();
}
}, "t1");
t1.start();
Thread.sleep(2000);
System.out.println("主線程進行打斷鎖");
t1.interrupt();
} finally {
// 主線程解鎖
System.out.println("主線程優(yōu)先釋放鎖");
lock.unlock();
}
}

圖片

  • 通過lockInterruptibly()?方法獲取鎖期間,可以通過線程的interrupt()方法進行中斷,跳出阻塞。
  • 通過lock()?方法獲取鎖,不會響應(yīng)interrupt()方法的中斷。

接下來我們看看它的實現(xiàn)原理。

public void lockInterruptibly() throws InterruptedException {    
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg) {
// 被其他線程打斷了直接返回 false
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
// 沒獲取到鎖,進入這里
doAcquireInterruptibly(arg);
}

先判斷一次線程是否中斷了,是的話,直接拋出中斷異常。

如果沒有獲取鎖,調(diào)用doAcquireInterruptibly()方法。

private void doAcquireInterruptibly(int arg) throws InterruptedException {
// 封裝當前線程,加入到隊列中
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
// 自旋
for (;;) {
// shouldParkAfterFailedAcquire判斷是否需要阻塞等待
// parkAndCheckInterrupt方法是阻塞線程,返回true,表示線程被中斷了
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
// 【在 park 過程中如果被 interrupt 會拋出異?!?span>, 而不會再次進入循環(huán)獲取鎖后才完成打斷效果
throw new InterruptedException();
}
} finally {
// 拋出異常前會進入這里
if (failed)
// 取消當前線程的節(jié)點
cancelAcquire(node);
}
}

addWaiter將當前線程封裝成節(jié)點,加入到隊列中。

shouldParkAfterFailedAcquire()方法判斷如果前一個節(jié)點的等待狀態(tài)時-1,則返回true,表示當前線程需要阻塞。

parkAndCheckInterrupt()?方法是阻塞線程,返回true,表示線程被中斷了,拋出InterruptedException異常。

最后調(diào)用cancelAcquire()方法,將當前節(jié)點狀態(tài)設(shè)置為cancel取消狀態(tài)。

// 取消節(jié)點出隊的邏輯
private void cancelAcquire(Node node) {
// 判空
if (node == null)
return;
// 把當前節(jié)點封裝的 Thread 置為空
node.thread = null;
// 獲取當前取消的 node 的前驅(qū)節(jié)點
Node pred = node.prev;
// 前驅(qū)節(jié)點也被取消了,循環(huán)找到前面最近的沒被取消的節(jié)點
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;

// 獲取前驅(qū)節(jié)點的后繼節(jié)點,可能是當前 node,也可能是 waitStatus > 0 的節(jié)點
Node predNext = pred.next;

// 把當前節(jié)點的狀態(tài)設(shè)置為 【取消狀態(tài) 1
node.waitStatus = Node.CANCELLED;

// 條件成立說明當前節(jié)點是尾節(jié)點,把當前節(jié)點的前驅(qū)節(jié)點設(shè)置為尾節(jié)點
if (node == tail && compareAndSetTail(node, pred)) {
// 把前驅(qū)節(jié)點的后繼節(jié)點置空,這里直接把所有的取消節(jié)點出隊
compareAndSetNext(pred, predNext, null);
} else {
// 說明當前節(jié)點不是 tail 節(jié)點
int ws;
// 條件一成立說明當前節(jié)點不是 head.next 節(jié)點
if (pred != head &&
// 判斷前驅(qū)節(jié)點的狀態(tài)是不是 -1,不成立說明前驅(qū)狀態(tài)可能是 0 或者剛被其他線程取消排隊了
((ws = pred.waitStatus) == Node.SIGNAL ||
// 如果狀態(tài)不是 -1,設(shè)置前驅(qū)節(jié)點的狀態(tài)為 -1
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
// 前驅(qū)節(jié)點的線程不為null
pred.thread != null) {

Node next = node.next;
// 當前節(jié)點的后繼節(jié)點是正常節(jié)點
if (next != null && next.waitStatus <= 0)
// 把 前驅(qū)節(jié)點的后繼節(jié)點 設(shè)置為 當前節(jié)點的后繼節(jié)點,【從隊列中刪除了當前節(jié)點】
compareAndSetNext(pred, predNext, next);
} else {
// 當前節(jié)點是 head.next 節(jié)點,喚醒當前節(jié)點的后繼節(jié)點
unparkSuccessor(node);
}
node.next = node; // help GC
}
}

鎖超時

ReentrantLock還具備鎖超時的能力,調(diào)用tryLock(long timeout, TimeUnit unit)方法,在給定時間內(nèi)獲取鎖,獲取不到就退出,這也是synchronized沒有的功能。

@Test
public void testLockTimeout() throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
// 調(diào)用tryLock獲取鎖
if (!lock.tryLock(2, TimeUnit.SECONDS)) {
System.out.println("t1獲取不到鎖");
return;
}
} catch (InterruptedException e) {
System.out.println("t1被打斷,獲取不到鎖");
return;
}
try {
System.out.println("t1獲取到鎖");
} finally {
lock.unlock();
}
}, "t1");
// 主線程加鎖
lock.lock();
System.out.println("主線程獲取到鎖");

t1.start();
Thread.sleep(3000);
try {
System.out.println("主線程釋放了鎖");
} finally {
lock.unlock();
}
}

那這個原理實現(xiàn)是什么樣的呢?

public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
// 調(diào)用tryAcquireNanos方法
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

public final boolean tryAcquireNanos(int arg, long nanosTimeout) {
if (Thread.interrupted())
throw new InterruptedException();
// tryAcquire 嘗試一次,獲取不到的話調(diào)用doAcquireNanos方法
return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
}

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
private boolean doAcquireNanos(int arg, long nanosTimeout) {    
if (nanosTimeout <= 0L)
return false;
// 獲取最后期限的時間戳
final long deadline = System.nanoTime() + nanosTimeout;
// 將當前線程添加到隊列中
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
// 自旋
for (;;) {
// 獲取前驅(qū)節(jié)點
final Node p = node.predecessor();
// 前驅(qū)節(jié)點是head,嘗試獲取鎖
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// 計算還需等待的時間
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L) //時間已到
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
// 如果 nanosTimeout 大于該值,才有阻塞的意義,否則直接自旋會好點
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// 【被打斷會報異?!?br> if (Thread.interrupted())
throw new InterruptedException();
}
}
}

如果nanosTimeout小于0,表示到了指定時間沒有獲取鎖成功,返回false

如果 nanosTimeout 大于spinForTimeoutThreshold,值為1000L,進行阻塞。因為時間太短阻塞沒有意義,否則直接自旋會好點。

總結(jié)

本文主要從使用到原理講解了ReentrantLock鎖的可重入、可打斷和鎖超時的特性,希望對大家有幫助。

責(zé)任編輯:武曉燕 來源: JAVA旭陽
相關(guān)推薦

2021-05-11 14:50:21

ReentrantLo可重入鎖Java

2024-01-30 08:41:33

線程執(zhí)行Redis分布式鎖

2021-12-15 07:49:22

Go語言設(shè)計

2020-06-15 08:15:47

分布式鎖系統(tǒng)

2021-06-27 21:24:55

RedissonJava數(shù)據(jù)

2021-07-08 09:21:17

ZooKeeper分布式鎖 Curator

2020-10-08 18:49:47

函數(shù)可重入不可重入

2021-03-11 08:55:47

JavaUser對象

2017-03-08 16:25:54

Linux多線程函數(shù)

2021-07-10 10:02:30

ZooKeeperCurator并發(fā)

2022-12-26 00:00:04

公平鎖非公平鎖

2020-11-10 07:46:58

函數(shù)printf 數(shù)據(jù)

2021-07-09 06:48:31

ZooKeeperCurator源碼

2020-08-24 08:13:25

非公平鎖源碼

2023-09-12 13:48:47

2023-04-06 00:15:03

JavaReentrantL線程

2024-02-04 09:29:07

Redis數(shù)據(jù)庫

2011-11-23 10:09:19

Java線程機制

2021-01-28 05:17:01

并發(fā)包JDK

2011-06-22 16:02:37

Qt 多線程 重入
點贊
收藏

51CTO技術(shù)棧公眾號