Java并发基础:深度解析Reentrant可重入性实现 - 程序员古德内容摘要
可重入锁有助于避免死锁,因为它允许线程在不释放已持有的锁的情况下,重新进入同步代码块,在某些情况下是非常必要的,
什么是可重入性?
锁的可重入性(Reentrant)是指同一个线程可以多次获取同一个锁,而不会导致死锁或其他线程无法获取该锁的情况,可重入锁是一种特殊的锁,它允许一个线程在已经持有该锁的情况下,再次获取(或重入)该锁,而不会产生冲突或死锁。
这种机制是通过为每个锁关联一个持有者和一个计数器来实现的,当线程首次获取锁时,它成为锁的持有者,并且计数器设置为1。如果同一个线程再次获取该锁,计数器就会增加,每次线程释放锁时,计数器都会减少,只有当计数器归零时,其他线程才有机会获取该锁。
可重入锁有助于避免死锁,因为它允许线程在不释放已持有的锁的情况下,重新进入同步代码块,在某些情况下是非常必要的,例如,在递归函数中,或者在需要调用其他也使用相同锁的方法时。
Java中的ReentrantLock类就是一个可重入锁的实现,此外,synchronized关键字在Java中提供的内置锁也是可重入的。
代码案例
下面是一个简单的代码示例,演示了ReentrantLock可重入锁的特性。
这个示例包括一个Counter类,它使用ReentrantLock来保护对内部计数器的访问,以及一个客户端类Client,它调用Counter的方法来增加计数器的值,如下代码:
import?java.util.concurrent.locks.ReentrantLock;
/**
*?@创建人?程序员古德
*?@创建时间?2024/1/18?23:51
*?@修改人?暂无
*?@修改时间?暂无
*?@版本历史?暂无
*/
//?Counter类使用ReentrantLock来保护计数器的状态
public?class?Counter?{
private?final?ReentrantLock?lock?=?new?ReentrantLock();
private?int?count?=?0;
//?增加计数器的值
public?void?increment()?{
lock.lock();?//?获取锁
try?{
count++;
System.out.println("Counter?incremented?by?"?+?Thread.currentThread().getName()?+?"?to?"?+?count);
}?finally?{
lock.unlock();?//?释放锁
}
}
//?递归增加计数器的值,演示可重入锁的特性
public?void?recursiveIncrement(int?depth)?{
if?(depth?<=?0)?{
return;
}
lock.lock();?//?在递归调用中重复获取锁
try?{
count++;
System.out.println("Counter?recursively?incremented?by?"?+?Thread.currentThread().getName()?+?"?to?"?+?count?+?"?at?depth?"?+?depth);
recursiveIncrement(depth?-?1);?//?递归调用
}?finally?{
lock.unlock();?//?递归返回时释放锁
}
}
}
//?客户端类,用于调用Counter的方法
public?class?Client?implements?Runnable?{
private?final?Counter?counter;
public?Client(Counter?counter)?{
this.counter?=?counter;
}
@Override
public?void?run()?{
//?调用increment方法
counter.increment();
//?调用recursiveIncrement方法进行递归增加
counter.recursiveIncrement(3);
}
public?static?void?main(String[]?args)?{
Counter?counter?=?new?Counter();
//?创建并启动两个客户端线程
Thread?clientThread1?=?new?Thread(new?Client(counter));
Thread?clientThread2?=?new?Thread(new?Client(counter));
clientThread1.start();
clientThread2.start();
//?注意:由于线程调度的不确定性,输出的顺序可能会有所不同
}
}
在上面代码中,Counter类有一个受ReentrantLock保护的count变量,increment方法简单地增加计数器的值,而recursiveIncrement方法递归地增加计数器的值,演示了同一个线程可以多次获取同一个锁而不会导致死锁的情况。程序运行结果如下:
Counter?incremented?by?Thread-0?to?1
Counter?recursively?incremented?by?Thread-0?to?2?at?depth?3
Counter?recursively?incremented?by?Thread-0?to?3?at?depth?2
Counter?recursively?incremented?by?Thread-0?to?4?at?depth?1
Counter?incremented?by?Thread-1?to?5
Counter?recursively?incremented?by?Thread-1?to?6?at?depth?3
Counter?recursively?incremented?by?Thread-1?to?7?at?depth?2
Counter?recursively?incremented?by?Thread-1?to?8?at?depth?1
这个输出显示了两个线程交替增加计数器的值,并且每个线程都能够递归地获取锁来增加计数器的值,而不会相互阻塞或导致死锁,这就是ReentrantLock可重入性的体现。
实现原理
Java并发基础:深度解析Reentrant可重入性实现 - 程序员古德
ReentrantLock的可重入性是通过其内部类Sync实现的,该类继承自AbstractQueuedSynchronizer(AQS),Sync有两个子类:NonfairSync和FairSync,分别表示非公平锁和公平锁,但它们在可重入性的实现上是相同的,可重入性的关键在于,当一个线程尝试获取锁时,如果锁已经被同一个线程持有,那么该线程可以再次获取锁而不会阻塞,这是通过在AQS中维护一个状态变量state和一个表示当前锁持有者的线程变量来实现的。
以下是ReentrantLock中与可重入性相关的关键代码段及其解释:
1、AQS的状态变量state:在AQS中,state变量用于表示同步状态,对于ReentrantLock,这个变量表示当前线程持有锁的重入次数。
2、Sync.tryAcquire(int acquires)方法:当线程尝试获取锁时,会调用此方法,它首先检查锁是否已经被当前线程持有,如果是,则增加重入计数,如果不是,则尝试获取锁。
如下代码案例:
protected?final?boolean?tryAcquire(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;
}
代码解释:
如果state为0,表示锁未被持有,尝试通过CAS操作将其设置为acquires(通常为1),并将当前线程设置为锁的独占所有者。
如果当前线程已经是锁的独占所有者,则增加state的值,表示重入次数增加。
如果其他线程尝试获取锁,则返回false。
3、Sync.tryRelease(int releases)方法:当线程释放锁时,会调用此方法,它减少重入计数,并在计数为0时释放锁,如下代码:
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;
setExclusiveOwnerThread(null);
}
setState(c);
return?free;
}
代码解释:
从state中减去releases(通常为1),表示减少重入次数。
检查当前线程是否是锁的独占所有者,如果不是,则抛出异常。
如果重入次数减至0,将锁的独占所有者设置为null,并标记free为true表示锁已被完全释放。
更新state的值。
领取专属 10元无门槛券
私享最新 技术干货