带着问题阅读
1.什么是可重入锁?可重入锁解决什么问题?
2.ReentrantLock的核心是AQS,它是怎么实现的?
3.ReentrantLock中的公平锁和非公平锁?
ReentrantLock是Java中实现可重入锁的一个重要类,下面是对其源码的简要解析。请注意,这里只是对关键部分进行了概要说明,具体实现细节可能会有更多复杂性。
公平锁和非公平锁
ReentrantLock可以是公平锁或非公平锁。在公平锁模式下,线程按照申请锁的顺序来获取锁,不会产生饥饿现象。而非公平锁则允许线程在任意时刻争夺锁,可能导致某些线程长时间无法获取锁,存在饥饿现象。
这个组件是基于AQS(AbstractQueuedSynchronizer)的抽象类,用于实现ReentrantLock
的同步机制。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
// lock方法是ReentrantLock类中用于获取锁的抽象方法
abstract void lock();
// 非公平锁的尝试获取锁的实现
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前状态
int c = getState();
if (c == 0) { //为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;
}
// 在共享模式下获取对象状态
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;
}
// 判断资源是否被当前线程占有
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
// 新生一个条件
final ConditionObject newCondition() {
return new ConditionObject();
}
// 返回占有资源的线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 返回状态
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// 资源是否被占有
final boolean isLocked() {
return getState() != 0;
}
// 自定义反序列化
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
方法 | 作用 |
---|---|
lock | lock 方法是ReentrantLock 类中用于获取锁的抽象方法。具体的实现在ReentrantLock 的非公平锁和公平锁中有所不同 |
nonfairTryAcquire | nonfairTryAcquire 方法是非公平锁的尝试获取锁的实现。它首先检查当前同步状态是否为0,如果是,则通过CAS操作将其设置为acquires ,表示获取锁成功。 |
tryRelease | tryRelease 方法是释放锁的实现。它首先检查当前线程是否为锁的持有者,如果不是则抛出IllegalMonitorStateException 异常。 |
isHeldExclusively | 用于检查当前线程是否是锁的持有者。这里直接比较当前线程和独占锁的持有者线程,如果相等,则当前线程是持有者。 |
newCondition | 用于创建一个与ReentrantLock 关联的Condition 对象,用于支持更灵活的线程等待和唤醒操作 |
getOwner | 返回当前持有锁的线程,如果锁未被持有则返回null |
getHoldCount | 返回当前线程持有锁的次数,如果不是持有者则返回0 |
isLocked | 用于检查锁是否被持有,即同步状态是否不为0 |
readObject | 用于反序列化,将实例的状态重置为未锁定状态 |
Sync是基于AbstractQueuedSynchronizer的抽象类,AQS是ReentrantLock的核心组件,它提供了一种便于实现各种同步器的框架。它基于FIFO队列实现了一套多线程同步的抽象框架,通过状态的获取和释放来实现线程的同步。
实现原理:
AQS使用一个int型的状态来表示资源的占用情况,通过CAS操作来保证状态的原子性。当线程尝试获取锁时,如果锁已被占用,线程会被加入到等待队列中。释放锁时,线程将释放锁的状态,并唤醒等待队列中的线程。
NonfairSync
是ReentrantLock
中非公平锁的实现,它继承了Sync
类。
// 非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
// 用于获取锁
final void lock() {
// 通过CAS操作将同步状态从0设置为1
if (compareAndSetState(0, 1))
// 设置成功,设置当前线程为独占锁的持有者
setExclusiveOwnerThread(Thread.currentThread());
else
// 进入等待队列等待获取锁
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
FairSync
是ReentrantLock
中公平锁的实现,它继承了Sync
类。
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// 直接调用了acquire方法,表示获取锁时要考虑等待队列中的其他线程,按照FIFO顺序获取锁
final void lock() {
acquire(1);
}
//
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前资源状态
int c = getState();
if (c == 0) {
// hasQueuedPredecessors 检查当前线程前面是否有等待线程
// compareAndSetState 比较和设置当前
// setExclusiveOwnerThread 记录当前线程是锁的持有者
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;
}
}
可重入锁是一种支持线程重复获取同一把锁的锁机制。当一个线程已经获得某个锁时,它可以再次获取该锁,而不会被阻塞。这种锁机制允许同一线程在持有锁的情况下多次进入同步块或方法,而不会被自己持有的锁所阻塞。
可重入锁解决了两个主要问题:
- 避免死锁: 在多线程环境中,如果一个线程已经获得了锁A,而在持有锁A的情况下又试图获取锁B,而另一个线程已经获得了锁B,又试图获取锁A,就会发生死锁。可重入锁允许同一线程在持有锁的情况下继续获取其他锁,避免了死锁的发生。
- 支持递归调用: 在某些情况下,同一线程需要多次进入同步块或方法,而不希望被自己持有的锁所阻塞。可重入锁允许同一线程多次获取同一把锁,使得递归调用的情况更容易管理。
public class FairLockExample {
private static final Lock fairLock = new ReentrantLock(true); // 公平锁
public static void main(String[] args) {
Runnable fairTask = () -> {
try {
fairLock.lock();
System.out.println(Thread.currentThread().getName() + " acquired the lock");
// 模拟对共享资源的操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " releasing the lock");
fairLock.unlock();
}
};
// 创建多个线程并启动
for (int i = 1; i <= 5; i++) {
new Thread(fairTask, "Thread-" + i).start();
}
}
}
Connected to the target VM, address: '127.0.0.1:62892', transport: 'socket'
Thread-1 acquired the lock
Thread-1 releasing the lock
Thread-2 acquired the lock
Thread-2 releasing the lock
Thread-3 acquired the lock
Thread-3 releasing the lock
Thread-4 acquired the lock
Thread-4 releasing the lock
Thread-5 acquired the lock
Thread-5 releasing the lock
Disconnected from the target VM, address: '127.0.0.1:62892', transport: 'socket'
public class NonfairLockExample {
private static final Lock nonfairLock = new ReentrantLock(); // 非公平锁
public static void main(String[] args) {
Runnable nonfairTask = () -> {
try {
nonfairLock.lock();
System.out.println(Thread.currentThread().getName() + " acquired the lock");
// 模拟对共享资源的操作
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " releasing the lock");
nonfairLock.unlock();
}
};
// 创建多个线程并启动
for (int i = 1; i <= 5; i++) {
new Thread(nonfairTask, "Thread-" + i).start();
}
}
}
Connected to the target VM, address: '127.0.0.1:50980', transport: 'socket'
Thread-1 acquired the lock
Thread-1 releasing the lock
Thread-3 acquired the lock
Thread-3 releasing the lock
Thread-2 acquired the lock
Thread-2 releasing the lock
Thread-4 acquired the lock
Thread-4 releasing the lock
Thread-5 acquired the lock
Thread-5 releasing the lock
Disconnected from the target VM, address: '127.0.0.1:50980', transport: 'socket'
synchronized
关键字: ReentrantLock
提供了比 synchronized
更灵活的锁控制,允许更复杂的线程同步结构。在需要更多控制权和灵活性的场景下,可以使用 ReentrantLock
替代 synchronized
。ReentrantLock
提供了可中断的锁请求功能,即线程可以响应中断信号而不是无限等待锁。这对于避免死锁等问题很有帮助。ReentrantLock
允许在尝试获取锁时设置超时时间,避免线程无限期地等待锁。ReentrantLock
提供了可选的公平锁和非公平锁。在公平锁模式下,等待时间较长的线程更有可能获取锁,有助于避免饥饿情况。ReentrantLock
支持通过 newCondition
方法创建与锁关联的 Condition
对象,从而实现更灵活的线程等待和通知机制。ReentrantLock
支持线程的可重入性,同一线程可以多次获取同一把锁,而不会造成死锁。ReentrantLock
提供了对锁释放的精确控制,可以在特定条件下选择是否释放锁,这对于实现一些特殊的同步逻辑很有帮助。ReentrantLock
可以提供更好的性能,特别是在高并发环境下。总的来说,ReentrantLock
适用于需要更灵活、更可控的线程同步机制的场景。在这些场景中,它提供了比传统的synchronized
关键字更多的功能和选项。