ReentrantLock(独占锁)
ReentrantLock(独占锁)
ReentrantLock 是Java中的一个工具类,位于 java.util.concurrent.locks 包下,是一种可重入的互斥锁,是 Lock
接口的一个实现。ReentrantLock
是由java提供的一种能够进行显示同步操作的锁,和synchronized不同的是,它是通过代码的方式来控制锁的获取和释放。
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void accessResource() {
lock.lock(); // 获取锁
try {
// 保护的代码块
System.out.println(Thread.currentThread().getName() + " is accessing the resource");
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) {
final ReentrantLockExample example = new ReentrantLockExample();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
example.accessResource();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
example.accessResource();
}
});
t1.start();
t2.start();
}
}
这个例子中,我们使用 ReentrantLock 来保护一个共享资源的访问,通过调用 lock 方法获取锁,然后在 try 代码块中访问共享资源,最后在
finally 代码块中释放锁,这样可以确保即使在访问共享资源时发生异常,也可以正确地释放锁。 main
方法中,我们创建了两个线程同时访问这个共享资源。由于我们对访问共享资源的方法使用了锁进行了保护,所以即使有多个线程同时访问,也可以保证每次只有一个线程能访问这个方法。
公平锁和非公平锁
ReentrantLock 在创建时可以通过构造函数传入 fair 参数来决定这把锁是公平锁还是非公平锁。如果不传,默认是非公平锁。
/**
* 公平锁
*/
private static ReentrantLock lock = new ReentrantLock(true);
/**
* 非公平锁
*/
private static ReentrantLock lock = new ReentrantLock();
公平锁
是指多个线程按照申请锁的顺序来获取锁,避免"饥饿"现象。在ReentrantLock内部,通过一个双向链表来管理等待锁的线程,越早等待的线程越早获取锁。
加锁底层实现
// 加锁逻辑
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取state状态
int c = getState();
if (c == 0) { // 没有线程持有锁
// cas尝试获取锁
if (compareAndSetState(0, acquires)) { // 加锁成功
// 标记当前线程,用来实现可重入锁
setExclusiveOwnerThread(current);
// 返回加锁成功
return true;
}
} else if (current == getExclusiveOwnerThread()) { // 有线程持有锁,判断是不是当前线程持有的
// 锁标识+1(可重入实现)
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 更新state状态
setState(nextc);
return true;
}
// 加锁失败
return false;
}
- 创建非公平的lock,调用lock.lock()进行加锁
- 调用CAS尝试加锁(修改state值由0->1,如果上一个持有锁的线程刚好释放,有可能会直接获取到)
- cas操作成功,state值为1,将当前线程赋值到exclusiveOwnerThread属性(独占线程表标识,做可重入锁的判断依据)
- cas操作失败,进入acquire(1)方法
- 调用tryAcquire(1)方法,首先判断state是否等于0,是则尝试加锁,否则判断当前线程是不是exclusiveOwnerThread标识的线程,是的话state值+1(可重入实现)
// 入队逻辑
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取前驱节点
final Node p = node.predecessor();
// 是头结点并且加锁成功了
if (p == head && tryAcquire(arg)) {
// 设置当前节点为头结点
setHead(node);
// 前驱节点的下一个节点置空,不在与当前节点有引用关系
p.next = null; // help GC
failed = false;
return interrupted;
}
// 阻塞线程前修改waitStatus,然后调用park()阻塞线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
解锁底层实现
// 解锁逻辑
protected final boolean tryRelease(int releases) {
// state-1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
// 默认false,有可能是重入锁,c != 0 说明当前线程还有是重入锁,还没有完全释放
boolean free = false;
if (c == 0) { // 锁释放成功
free = true;
setExclusiveOwnerThread(null);
}
// 更新state状态
setState(c);
return free;
}
- 调用lock.unlock()
- 调用release(1)方法
- 调用tryRelease(1)方法,首先获取state执行-1操作,如果-1后的值为0,将exclusiveOwnerThread置空,最后将-1后的值赋值到state,释放锁
- 锁释放成功,唤醒队列中下一个线程节点,当前线程出队
非公平锁
是指多个线程获取锁的顺序并不是按照申请锁的顺序进行的。这样可能造成优先级高的线程因为等待而导致一种称为“饥饿”的现象,但是相对于公平锁,非公平锁的性能上要高。在ReentrantLock内部,如果有线程试图获取锁,且锁当前未被其他线程持有,那么无论等待队列中是否有线程在等待,该线程都能立即获取到锁,即 "
插队"。
实现原理
公平锁的实现与非公平锁实现原理类似,只不过在加锁之后,直接就调用了acquire(1)方法,而非公平锁再进入acquire(1)方法之前还会去尝试加锁。
注意
- 在等待队列中的线程时,一定是唤醒下标为1的,因为0是记录当前获取锁的线程
- 非公平的实现,只针对于线程入队之前,线程入队后还是会遵循队列的先进先出原则
使用场景
- 多线程并发控制
- 超时等待
- 可中断锁实现
- 公平锁
- 多条件锁实现