简单介绍
ReentrantLock 是一个可重入的独占锁
-
可重入
同一线程外层函数获得锁之后,内层递归函数仍然可以获取该锁的代码
该特性带来的两个问题:- 如何识别获取锁的线程是否为当前占据锁的线程
- 线程重复 n 次获取了锁,需要释放 n 次锁,否则会导致别的线程无法获得锁
-
独占
一次只能被一个线程所持有
类型
private final Sync sync;
ReentrantLock 的内部类 Sync 继承了 AQS(AbstractQueuedSynchronizer),并且有公平锁 FairSync 和 非公平锁 NonfaireSync 两个字类,ReentrantLock 的获取与释放锁操作都是委托给该同步组件来实现的
(注:该篇文章里所有 AQS 相关的内容会再写一篇相关的文章,这里不详细介绍)
- 公平锁
是指当锁可用时,在锁上等待时间最长的线程将获取锁的使用权(先来先得)
使用有参构造方法,传入true创建公平锁public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } - 非公平锁
随机分配使用权,使用ReentrantLock无参的构造函数,默认创建的是非公平锁public ReentrantLock() { sync = new NonfairSync(); }
常用方法介绍
-
lock()方法获取锁,如果获取不到锁,则当前线程在获取到锁之前都不可调度(不响应中断)public void lock() { sync.lock(); } -
lockInterruptibly()方法获取锁,则当前线程在获取到锁之前都不可调度,除非有其他线程中断了当前线程(响应中断)public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } -
tryLock()方法获取锁,如果调用的时候能够获取锁,那么就获取锁并且返回true,如果当前的锁无法获取到,那么这个方法会立刻返回falsepublic boolean tryLock() { return sync.nonfairTryAcquire(1); } -
tryLock(long timeout, TimeUnit unit)方法获取锁,在指定时间内尝试获取锁。如果可以获取锁,则获取锁并返回true,如果无法获取锁,则当前线程变为不可调度,除非当前线程被中断或者到了指定的等待时间public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } -
unlock()释放锁,释放当前线程占用的锁。注意:获取了几次锁,就要释放几次锁public void unlock() { sync.release(1); } -
newCondition()方法,返回一个与当前的锁关联的条件变量。在使用这个条件变量之前,当前线程必须占用锁。调用Condition的await方法,会在等待之前原子地释放锁,并在等待被唤醒后原子的获取锁public Condition newCondition() { return sync.newCondition(); } -
isHeldByCurrentThread()方法,查询当前线程是否保持锁定public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } -
isLocked()方法,查询该锁是否已经被锁定public boolean isLocked() { return sync.isLocked(); } -
isFair()方法,判断锁是公平锁还是非公平锁public final boolean isFair() { return sync instanceof FairSync; }
Sync 内部类
abstract static class Sync extends AbstractQueuedSynchronizer {
...
}
Sync 是一个抽象类型,它继承 AbstractQueuedSynchronizer,这个 AbstractQueuedSynchronizer 是一个模板类,它实现了许多和锁相关的功能,并提供了钩子方法供用户实现,比如 tryAcquire,tryRelease 等
static final class NonfairSync extends Sync {
final void lock() {
...
}
}
static final class FairSync extends Sync {
final void lock() {
...
}
}
NonfairSync 和 FairSync 两个类继承自 Sync ,实现了 lock 方法
-
lock
当我们调用ReentrantLock的lock方法的时候,实际上是调用了NonfairSync或者FairSync的lock方法-
NonfairSync非公平锁的lock实现final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
CAS操作,去尝试抢占该锁。如果成功,就把当前线程设置在这个锁上,表示抢占成功。如果失败,则调用acquire模板方法,等待抢占
acquire方法里调用了tryAcquire(int arg)方法,NonfairSync的tryAcquire实际上又调用的Sync的nonfairTryAcquire方法,如下final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 判断锁的状态是不是 0,如果是,则尝试去原子抢占这个锁 if (compareAndSetState(0, acquires)) { // 如果抢占到了,把状态设置为1 setExclusiveOwnerThread(current); // 并且设置当前线程为独占线程 return true; } } else if (current == getExclusiveOwnerThread()) {// 如果锁的状态不为 0,判断该线程是否是独占线程(可重入) int nextc = c + acquires; // 如果当前线程是独占线程,则增加状态变量的值 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); // 给状态变量赋值 return true; } return false; }tryAcquire一旦返回false,就会则进入acquireQueued流程,此段代码中锁的获取可以分为两种情况:-
state为0时:代表锁已经释放,可以去获取,所以使用
CAS去获取锁,如果获取成功,则代表竞争锁成功,调用setExclusiveOwnerThread设置当前线程为独占线程,因为队列中的线程和新线程都可以CAS获取锁不需要排队,所以是非公平锁 -
status不为0时:代表锁已经被占有,如果当前线程是占有锁的线程(
current == getExclusiveOwnerThread()为true),更新state,意味着当前线程又一次的获取了锁,这就是可重入。
-
FairSync公平锁的lock实现final void lock() { acquire(1); }
acquire方法里同样调用了tryAcquire(int arg)方法,FairSync的tryAcquire方法实现如下:protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 判断锁的状态是不是 0,如果是 0,再判断是否有线程在排队获取锁 if (!hasQueuedPredecessors() && // 如果没有线程在排队获取锁则尝试原子抢占锁 compareAndSetState(0, acquires)) { // 如果抢占到了,把状态设置为1 setExclusiveOwnerThread(current); // 并且设置当前线程为独占线程 return true; } } else if (current == getExclusiveOwnerThread()) { // 如果锁的状态不为 0,判断该线程是否是独占线程(可重入) int nextc = c + acquires; // 如果当前线程是独占线程,则增加状态变量的值 if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); // 给状态变量赋值 return true; } return false; }tryAcquire一旦返回false,就会则进入acquireQueued流程,公平锁获取锁的过程与非公平锁不一样的地方在 state为0时 新线程需要判断有没有线程在排队获取锁,只有当没有的时候才会去尝试抢占锁,如果有线程在排队,新线程也会被加入到排队的队列中去 -
-
unlock
unlock方法,其实是直接调用AbstractQueuedSynchronizer的release操作,release方法先调用了tryRelease方法,Sync的tryRelease方法实现如下:
一旦protected final boolean tryRelease(int releases) { int c = getState() - releases; //状态变量值减少,这里是考虑到可重入锁可能自身会多次占用锁 if (Thread.currentThread() != getExclusiveOwnerThread()) // 如果当前线程不是独占线程则抛异常 throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { // 当状态值为 0 ,锁释放 free = true; setExclusiveOwnerThread(null); // 将独占线程设置为 null } setState(c); // 状态变量赋值 return free; }tryRelease成功,下一个节点的线程被唤醒,被唤醒的线程就会进入acquireQueued流程中,去获取锁