ReentrantLock的lock(), tryLock(), tryLock(long timeout, TimeUnit unit), lockInterruptibly() 及使用場景示例


本文源自:https://blog.csdn.net/michaelgo/article/details/81481068

1.ReentrantLock簡要介紹
簡單介紹一下ReentrantLock,可重入鎖,互斥鎖,提供了fair和unfair兩種模式的鎖。默認構造函數是unfair的鎖,如果初始化時傳入true的參數則會返回fair鎖。所謂不公平就是在鎖可獲取時,不用考慮該鎖隊列是否有其他waiter,直接獲取;反之,對於公平鎖來講就是當等待的鎖資源可獲取時要看下等待隊列中當前線程是不是head線程,如果不是則不獲取。

簡單的看一下獲取鎖的代碼:

//fair lock tryAcquire
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//unfair lock nonfairTryAcquire
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
從上面的代碼塊可以清晰的看到,公平鎖fairLock在獲取之前會看下自己是不是沒有前繼元素。而非公平鎖unfairLock並不會。

2.ReentrantLock提供的獲取鎖的方式
ReentrantLock提供了lock()、tryLock()、tryLock(long timeout, TimeUnit unit)、lock.lockInterruptibly()

1)lock()

public void lock() {
sync.lock();
}
當鎖可用,並且當前線程沒有持有該鎖,直接獲取鎖並把count set為1.
當鎖可用,並且當前線程已經持有該鎖,直接獲取鎖並把count增加1.
當鎖不可用,那么當前線程被阻塞,休眠一直到該鎖可以獲取,然后把持有count設置為1.
小結:該種方式獲取鎖不可中斷,如果獲取不到則一直休眠等待。

2)tryLock() 

public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
當獲取鎖時,只有當該鎖資源沒有被其他線程持有才可以獲取到,並且返回true,同時設置持有count為1;
當獲取鎖時,當前線程已持有該鎖,那么鎖可用時,返回true,同時設置持有count加1;
當獲取鎖時,如果其他線程持有該鎖,無可用鎖資源,直接返回false,這時候線程不用阻塞等待,可以先去做其他事情;
即使該鎖是公平鎖fairLock,使用tryLock()的方式獲取鎖也會是非公平的方式,只要獲取鎖時該鎖可用那么就會直接獲取並返回true。這種直接插入的特性在一些特定場景是很有用的。但是如果就是想使用公平的方式的話,可以試一試tryLock(0, TimeUnit.SECONDS),幾乎跟公平鎖沒區別,只是會監測中斷事件。
3)tryLock(long timeout, TimeUnit unit)

public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
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 > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
從上面代碼中可以看出,獲取鎖成功或者超時之后返回。而且在公平鎖和非公平鎖的場景下都可以使用,只是會增加對中斷事件的監測。
當獲取鎖時,鎖資源在超時時間之內變為可用,並且在等待時沒有被中斷,那么當前線程成功獲取鎖,返回true,同時當前線程持有鎖的count設置為1.
當獲取鎖時,在超時時間之內沒有鎖資源可用,那么當前線程獲取失敗,不再繼續等待,返回false.
當獲取鎖時,在超時等待時間之內,被中斷了,那么拋出InterruptedException,不再繼續等待.
當獲取鎖時,在超時時間之內鎖可用,並且當前線程之前已持有該鎖,那么成功獲取鎖,同時持有count加1.
4)lockInterruptibly()

public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
當獲取鎖時,鎖資源可用,那么當前線程成功獲得鎖,同時持有count設置為1,返回true.
當獲取鎖時,鎖資源可用,當前線程已持有該鎖,它成功獲取該鎖,同時持有count增加1,返回true.
當獲取鎖時,鎖資源不可用,那么該線程開始阻塞休眠等待,但是等待過程中如果有中斷事件,那么會停止等待,立即返回.
當獲取鎖時,鎖資源不可用,線程開始阻塞休眠等待,如果等待過程中鎖資源變為可用,那么當前線程成功獲得鎖,同時持有count設置為1,返回true.
小結:lockInterruptibly()獲取鎖是以排他的模式獲取,一旦被中斷就放棄等待獲取。在等待開始時首先檢測中斷狀態,然后至少調用一次tryAcquire,成功獲取就返回true。否則當前線程就開始排隊,並且不斷的被blocking、unblocking、invoking tryAcquire 直到獲取成功或者被中斷為止。

3.使用場景示例
下面提供三個demo分別使用lock(), tryLock()的方式獲取鎖資源,示例展示了持續等待,立即返回,限時等待,中斷等場景的使用。

1.如果當前獲得鎖的線程在做大量耗時的工作,使用lock.lock()方法申請鎖的線程會一直阻塞,這樣就降低了多線程的效率。而使用tryLock()方法申請鎖,如果鎖不可用則線程不會阻塞,轉而可以去做其他工作。代碼實例如下:

public class TestLockAndTryLock {
private ReentrantLock rlock = new ReentrantLock();

private void lockTest(){
long currentTime = System.currentTimeMillis();
try {
rlock.lock();

while (System.currentTimeMillis() - currentTime <= 1000){
//assume do something
}
System.out.println("lockTest----current thread get the lock: " + Thread.currentThread().getName());
}finally {
rlock.unlock();
System.out.println("lockTest----current thread release the lock: " + Thread.currentThread().getName());
}
}

private void tryLockTest(){

long currentTime = System.currentTimeMillis();

while (System.currentTimeMillis() - currentTime <= 100){
//assume do something
}

if (rlock.tryLock()){
try {
System.out.println("tryLockTest----current thread get the lock: " + Thread.currentThread().getName());

}finally {
rlock.unlock();
System.out.println("tryLockTest----current thread release the lock: " + Thread.currentThread().getName());
}

}else {
System.out.println("tryLockTest----current thread CAN NOT get the lock: " + Thread.currentThread().getName());
}
}

public static void main(String[] args){

TestLockAndTryLock lockAndTryLock = new TestLockAndTryLock();

Thread lockThread = new Thread(
() -> lockAndTryLock.lockTest(), "Lock-Thread" );

Thread tryLockThread = new Thread(
() -> lockAndTryLock.tryLockTest(), "TryLock-Thread" );

tryLockThread.start();
lockThread.start();

}

}
output:

tryLockTest----current thread CAN NOT get the lock: TryLock-Thread
lockTest----current thread get the lock: Lock-Thread
lockTest----current thread release the lock:  Lock-Thread
lock方法不能被中斷。如果一個線程在等待獲得一個鎖時被中斷,中斷線程在獲得鎖之前會一直處於 阻塞狀態。如果出現死鎖,那么lock方法就無法被終止。但是tryLock(long,TimeUnit)在等待超時之后可以結束等待。demo如下:

public class TestLockAndTryLock {
private ReentrantLock rlock = new ReentrantLock();

private void lockTest(){
long currentTime = System.currentTimeMillis();
try {
rlock.lock();

System.out.println("lockTest----current thread get the lock: " + Thread.currentThread().getName());

while (System.currentTimeMillis() - currentTime <= 5000){
//assume do something
}

}finally {
rlock.unlock();
System.out.println("lockTest----current thread release the lock: " + Thread.currentThread().getName());
}
}

private void tryLockTest(){

long currentTime = System.currentTimeMillis();

while (System.currentTimeMillis() - currentTime <= 100){
//assume do something
}

if (rlock.tryLock()){
try {
System.out.println("tryLockTest----current thread get the lock: " + Thread.currentThread().getName());


}finally {
rlock.unlock();
System.out.println("tryLockTest----current thread release the lock: " + Thread.currentThread().getName());
}

}else {
System.out.println("tryLockTest----current thread CAN NOT get the lock: " + Thread.currentThread().getName());
}
}


private void tryLockInterruptTest(){

long currentTime = System.currentTimeMillis();

while (System.currentTimeMillis() - currentTime <= 100){
//assume do something
}

try {
System.out.println("Begin time: " + System.currentTimeMillis());
if (rlock.tryLock(1, TimeUnit.SECONDS)){
try {
System.out.println("tryLockInterruptTest----current thread get the lock: " + Thread.currentThread().getName());

}finally {
rlock.unlock();
System.out.println("tryLockInterruptTest----current thread release the lock: " + Thread.currentThread().getName());
}

}else {
System.out.println("End time: " + System.currentTimeMillis());
System.out.println("tryLockInterruptTest----current thread CAN NOT get the lock: " + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void main(String[] args){

TestLockAndTryLock lockAndTryLock = new TestLockAndTryLock();

Thread lockThread = new Thread(
() -> lockAndTryLock.lockTest(), "Lock-Thread" );

Thread tryLockInterruptThread = new Thread(
() -> lockAndTryLock.tryLockInterruptTest(), "TryLockInterrupt-Thread"
);

tryLockInterruptThread.start();
lockThread.start();

}

}
output:
lockTest----current thread get the lock: Lock-Thread
Begin time: 1533636472680
End time: 1533636473681
tryLockInterruptTest----current thread CAN NOT get the lock: TryLockInterrupt-Thread
lockTest----current thread release the lock: Lock-Thread
同時,tryLock(long, TimeUnit)可以被中斷,demo如下:

private void tryLockInterruptTest(){

long currentTime = System.currentTimeMillis();

while (System.currentTimeMillis() - currentTime <= 100){
//assume do something
}

try {
System.out.println("Begin time: " + System.currentTimeMillis());
if (rlock.tryLock(3, TimeUnit.SECONDS)){
try {
System.out.println("tryLockInterruptTest----current thread get the lock: " + Thread.currentThread().getName());

}finally {
rlock.unlock();
System.out.println("tryLockInterruptTest----current thread release the lock: " + Thread.currentThread().getName());
}

}else {
System.out.println("End time: " + System.currentTimeMillis());
System.out.println("tryLockInterruptTest----current thread CAN NOT get the lock: " + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
System.out.println("tryLockInterruptTest Interrupt----current thread is interrupted: " + Thread.currentThread().getName());
}
}

public static void main(String[] args){

TestLockAndTryLock lockAndTryLock = new TestLockAndTryLock();

Thread lockThread = new Thread(
() -> lockAndTryLock.lockTest(), "Lock-Thread" );

Thread tryLockInterruptThread = new Thread(
() -> lockAndTryLock.tryLockInterruptTest(), "TryLockInterrupt-Thread"
);

tryLockInterruptThread.start();
lockThread.start();

try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "is interrupted now. ");
}

tryLockInterruptThread.interrupt();

}
output:

lockTest----current thread get the lock: Lock-Thread
Begin time: 1533637530378
tryLockInterruptTest Interrupt----current thread is interrupted: TryLockInterrupt-Thread
lockTest----current thread release the lock: Lock-Thread
很明顯被中斷了,沒有完成等待。

以上是對ReentrantLock的幾種獲取鎖的方法的詳解,並附以demo示例,如有疑問可以留言討論。
————————————————
版權聲明:本文為CSDN博主「michaelgo」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/michaelgo/java/article/details/81481068


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM