章
目
录
本文重点讲解ReentrantLock的底层原理以及优缺点内容,为了深入了解Reentrant Lock的底层原理,我们需要从几个关键方面入手:锁的状态管理、可重入性、公平性与非公平性、以及条件变量的使用。接下来,我将详细解释这些方面。
1. 锁的状态管理
Reentrant Lock使用一个状态变量来表示锁的持有情况。这个状态通常用来表示锁被重入的次数。
// AbstractQueuedSynchronizer类中的部分代码
abstract static class Sync extends AbstractQueuedSynchronizer {
// ...
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
}
在上面的代码片段中,AbstractQueuedSynchronizer
(AQS)是Reentrant Lock实现的基础。它使用getExclusiveOwnerThread()
方法来检查当前线程是否是锁的持有者。
拓展(AbstractQueuedSynchronizer)
AbstractQueuedSynchronizer
(简称AQS)是Java并发包中的一个核心类,它提供了一种有效的框架来实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关的同步器(如信号量、事件等)。它是许多同步类的基础,包括ReentrantLock
、Semaphore
、CountDownLatch
和FutureTask
。
原理和结构
状态(State): AQS使用一个整型的变量来表示同步状态。这个状态是同步器的核心,用于控制同步器是否被占用。
节点(Node)和等待队列: 当线程尝试获取同步状态失败时,AQS会将该线程包装成一个节点(Node)并将其加入到队列中。这个队列是一个FIFO队列,用于管理等待获取同步状态的线程。
独占模式和共享模式: AQS支持两种同步模式。独占模式在任何时候只允许一个线程持有同步状态,而共享模式允许多个线程同时持有同步状态。
核心方法
获取和释放方法:
acquire(int arg)
和release(int arg)
用于实现独占式的获取和释放同步状态。
acquireShared(int arg)
和releaseShared(int arg)
用于实现共享式的获取和释放同步状态。
同步状态管理方法:
getState()
, setState(int newState)
, 和compareAndSetState(int expect, int update)
用于管理同步状态。
等待队列管理方法:
hasQueuedThreads()
和hasContended()
用来检查是否有线程在等待队列中。
使用AQS的优点
降低复杂性: AQS抽象了一系列复杂的同步控制逻辑,使得开发者可以通过实现几个方法来快速创建可靠的同步器。
高效和可靠: AQS提供的框架经过了良好的测试和优化,能够有效地管理线程间的同步。
灵活性: 支持独占和共享两种模式,可以满足不同场景的同步需求。
AQS的缺点
复杂性: 尽管AQS降低了实现同步器的复杂度,但理解其内部工作原理仍然需要一定的并发编程知识。
直接使用局限性: AQS是一个用于构建锁和同步器的框架,它本身在大多数情况下不直接用于编程。
结论
AbstractQueuedSynchronizer
是Java并发编程中的一个重要组件。它通过提供一组方法来管理同步状态、实现锁的获取与释放,并管理等待获取锁的线程队列,从而为开发高效且可靠的同步器提供了坚实的基础。尽管它的直接使用相对复杂,但它背后的概念和机制对理解Java并发编程至关重要。
2. 可重入性
Reentrant Lock的可重入性是指同一个线程可以多次获取同一个锁。这是通过在锁内部记录锁的所有者以及锁的重入次数来实现的。
public void lock() {
sync.lock();
}
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;
}
在这段代码中,tryAcquire
方法首先检查锁是否已经被占用(即状态c
不为0)。如果没有被占用,它尝试设置当前线程为锁的所有者。如果已经被当前线程占用,它简单地增加重入计数。
3. 公平性与非公平性
Reentrant Lock提供了公平和非公平两种模式。在公平模式下,等待最久的线程会优先获取锁。而在非公平模式下,这种排序不被保证。
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}
在这里,FairSync
类扩展了Sync
,并重写了tryAcquire
方法以添加检查是否有等待更久的线程。
4. 条件变量
Reentrant Lock使用Condition
对象来支持线程间的协作,这允许在特定条件下挂起和通知线程。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
public Condition newCondition() {
return sync.newCondition();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final ConditionObject newCondition() {
return new ConditionObject();
}
}
}
在上述代码中,newCondition()
方法创建一个新的ConditionObject
实例,它是AbstractQueuedSynchronizer.ConditionObject
的一个实例,提供了等待/通知的功能。
当讨论Reentrant Lock(可重入锁)的优势和劣势时,我们需要从多个角度来考虑,包括它在性能、灵活性、易用性以及其它方面的表现。
5.ReentrantLock优点
灵活性与功能性
- 可中断的锁获取: Reentrant Lock允许在等待锁的过程中响应中断,这是
synchronized
无法提供的功能。 - 尝试锁定与超时: 提供了
tryLock()
方法,可以尝试获取锁而不是无限期等待,还可以设置超时时间。 - 公平锁选项: 可以选择创建一个公平锁,确保按照请求的顺序获得锁,而
synchronized
总是非公平的。 - 支持多个条件变量: Reentrant Lock可以与多个
Condition
实例一起使用,允许更复杂的线程间协调。
性能
在高并发环境中,特别是锁竞争不是特别激烈的情况下,Reentrant Lock通常提供比synchronized
更好的性能。
可重入性
允许同一个线程多次获得同一把锁,减少了死锁的风险。
6.ReentrantLock缺点
复杂性
相对于synchronized
,Reentrant Lock的使用更复杂,需要手动管理锁的获取和释放,增加了编程的复杂性。
风险
- 错误的使用(如忘记释放锁)可能导致严重问题,例如死锁或资源泄漏。
- 由于Reentrant Lock不是自动管理的,因此在异常处理中必须非常小心,确保在
finally
块中释放锁。
资源消耗
相比synchronized
,Reentrant Lock可能在某些情况下更加消耗资源,特别是在使用公平锁的情况下。
锁的升级和降级不支持
Reentrant Lock不支持锁的升级(从读锁升级为写锁)和降级(从写锁降级为读锁),这在某些场景下可能是一个限制。
7.总结
Reentrant Lock在灵活性、功能性和性能方面提供了显著的优势,特别适合于需要高级同步特性的复杂应用场景。然而,这些优势也伴随着更高的复杂性和潜在的风险。
因此,选择Reentrant Lock还是synchronized
需要根据具体的应用场景和需求来决定。简单场景下,使用synchronized
可能更为合适,因为它更简单且在JVM层面得到了优化。而在复杂的多线程应用中,Reentrant Lock的高级功能可能更有价值。
以上就是ReentrantLock的底层原理以及优缺点的全部内容,你学会了吗?