ReentrantLock
简介
- ReentrantLock重入锁,是实现Lock接口的一个类。
- 支持重入性(表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞,synchronized隐式支持重入性)
- ReentrantLock还支持公平锁和非公平锁两种方式。
ReentrantLock是J.U.C包下提供的独占锁锁,根据其名称可知,该锁是可重入的。
如果线程重入了一个锁,那么ReentrantLock的锁计数器会加1,每个解锁请求,锁计数器减1。当锁计数器为0的时候,表示资源被解锁了。
synchronized通过操作Mark Word实现同步,锁标识存储于Mark Word中,锁的获取和释放都是隐式的,实现的原理是通过编译后加上不同的机器指令来实现;ReentrantLock通过AQS(抽象同步队列)实现同步,锁标识存储于AQS的state属性中。
ReentrantLock在上锁时,会根据实例化时指定的策略去获取锁,默认为非公平锁。如果上锁成功,锁状态值+1(重入,最大次数为 Integer.MAX_VALUE),并将锁持有者设置为当前线程实例。在 Sync 内部维护了一个队列,存放了所有上锁失败的线程。公平锁在上锁前,会检查在自己前面是否还有其他线程等待,如果有就放弃竞争,继续等待。而非公平锁会抓住每个机会,不管是否前面是否还有其它线程等待,只顾上锁
ReetrantLock在释放锁时,将状态计数器减一(重入),当状态计数器为0时,锁可用。此时再从等待队列中寻找合适的线程唤醒,默认从队首开始,如果队列正在更新中,且未找到合适的线程,那么从队尾开始寻找。
ReentrantLock实现的锁又可以分为两类,分别是公平锁和非公平锁,分别由ReentrantLock类中的两个内部类FairSync和NonfairSync来实现。FiarSync和NonfairSync均继承了Sync类,而Sync类又继承了AbstractQueuedSynchronizer(AQS)类,所以ReentrantLock最终是依靠AQS来实现锁的。
重要方法
public class ReentrantLock implements Lock, java.io.Serializable {}
// 获得锁方法,获取不到锁的线程会到同步队列中阻塞排队 void lock(); // 获取可中断的锁 void lockInterruptibly() throws InterruptedException; // 尝试获得锁,如果锁空闲,立马返回 true,否则返回 false boolean tryLock(); // 带有超时等待时间的锁,如果超时时间到了,仍然没有获得锁,返回 false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 释放锁 void unlock(); // 得到新的 Condition Condition newCondition();
// 同步器 Sync 的两个子类锁 static final class FairSync extends Sync {} static final class NonfairSync extends Sync {} abstract static class Sync extends AbstractQueuedSynchronizer {} |
构造器
ReentrantLock 构造器有两种
//无参数构造器,相当于 ReentrantLock(false),默认是非公平的 public ReentrantLock() { sync = new NonfairSync(); }
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
公平锁 FairSync 公平锁只实现了 lock 和 tryAcquire 两个方法,lock 方法非常简单,如下:
// acquire 是 AQS 的方法,表示先尝试获得锁,失败之后进入同步队列阻塞等待
final void lock() { acquire(1); }
在 FairSync 并没有重写 acquire 方法代码。调用的为 AbstractQueuedSynchronizer 的代码,如下:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } |
使用
Lock lock = new ReentrantLock();
lock.lock();
try {
doSomething();
}finally {
lock.unlock();
}
锁实现
AbstractQueuedSynchronizer的实现类,锁的获取和释放都是基于Sync,对AbstractQueuedSynchronizer不清楚的,可以看下另一篇
abstract static class Sync extends AbstractQueuedSynchronizer {
//抽象方法,由NonfairSync和FairSync进行实现,公平的获取锁,还是非公平的获取锁 abstract void lock();
//在NonfairSync中使用到,非公平的获取锁 //@param acquires 要获取的锁数 final boolean nonfairTryAcquire(int acquires) { //获取当前要加锁的线程 final Thread current = Thread.currentThread(); //获取锁的状态,即AQS的属性state值 int c = getState(); //如果锁的状态等于0,表示处于无锁状态 if (c == 0) { //使用CAS更新锁状态,将锁状态更新成要获取的锁数 if (compareAndSetState(0, acquires)) { //如果CAS更新锁状态成功,表示获取锁成功,将当前线程设置为占有锁的线程,即设置属性exclusiveOwnerThread为当前线程 setExclusiveOwnerThread(current); //返回加锁成功 return true; } } //当前锁已被占有,判断占有锁的线程是否是当前线程,如果不是直接返回获取锁失败 else if (current == getExclusiveOwnerThread()) { //锁的原有状态加上传入进来要获取的锁数得到新的锁状态值 int nextc = c + acquires; //如果计算出的状态值是负数,直接抛出Error错误,但是感觉这里会有些问题,比如原来的锁状态值为1,传入-1也会把锁给释放掉,这样加锁操作就变成了释放锁操作 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); //设置锁的状态值为新的状态值nextc setState(nextc); //返回获取锁成功 return true; } //返回获取锁失败 return false; }
//此方法在ReentrantLock的unLock方法中使用到,释放锁,修改锁的状态 //此方法只能在占有锁的线程调用,即unLock方法只能在持有锁的线程进行锁的释放 //@param releases 要释放的锁数 protected final boolean tryRelease(int releases) { //得到锁的新状态值 int c = getState() - releases; //如果当前线程不是持有锁的线程,直接抛出IllegalMonitorStateException异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); //释放锁是否成功的标志位 boolean free = false; //如果新的锁状态值为0 if (c == 0) { //将释放锁是否成功的标志位设置为成功 free = true; //将占有独占锁的线程,即属性exclusiveOwnerThread置为空 setExclusiveOwnerThread(null); } //设置锁的状态 setState(c); //返回释放锁成功 return free; }
//判断当前线程是否是持有锁的线程,如果是返回true,否则返回false protected final boolean isHeldExclusively() { //返回当前线程是否是持有锁的线程 return getExclusiveOwnerThread() == Thread.currentThread(); }
//创建条件变量实例ConditionObject final ConditionObject newCondition() { //返回新建的ConditionObject实例 return new ConditionObject(); }
//获取占有锁的线程 final Thread getOwner() { //如果当前处于无锁状态,返回null,否则返回占有锁的线程 return getState() == 0 ? null : getExclusiveOwnerThread(); }
//得到锁的被获取数,也是锁的状态,只能在持有锁的线程操作才能获取到锁的状态,即锁的被获取数,否则直接返回0 final int getHoldCount() { //只能在持有锁的线程操作才能获取到锁的状态,即锁的被获取数,否则直接返回0 return isHeldExclusively() ? getState() : 0; }
//判断锁是否有被线程占有,即锁的状态是否是处于加锁的状态 final boolean isLocked() { //锁的状态不等于0,表明锁被线程占有,锁状态处于加锁状态 return getState() != 0; }
//从工作流中得到锁的对象,此方法目前没有使用到 private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); //重新设置锁的状态 setState(0); // reset to unlocked state } } |
NonfairSync
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L;
//Sync的抽象lock方法的重写,非公平的获取锁,在Reentrantlock的lock方法使用到 final void lock() { //使用CAS将锁的状态从0更新成1,即加锁操作 if (compareAndSetState(0, 1)) //如果加锁成功,将当前线程设置为占有锁的线程,即设置属性exclusiveOwnerThread为当前线程 setExclusiveOwnerThread(Thread.currentThread()); else //NonfairSync从AQS中继承下来的方法,下面在讲锁的获取时会进行详细的介绍 acquire(1); }
//NonfairSync重写了AbstractQueuedSynchronizer的tryAcquire模板方法,否则AQS中的tryAcquire方法会直接抛出 UnsupportedOperationException异常 //tryAcquire方法在acquire中使用到,非公平的获取锁都是基于此方法 //@param acquires 要获取的锁数 protected final boolean tryAcquire(int acquires) { //nonfairTryAcquire方法,在上面Sync内部中有进行介绍,非公平的获取锁,无需判断同步队列中前面是否有节点也在获取锁 return nonfairTryAcquire(acquires); } } |
FairSync
static final class FairSync extends Sync { //Sync的抽象lock方法的重写,公平的获取锁,在Reentrantlock的lock方法使用到 //FairSync的lock方法和NonfairSync的lock方法的区别是,NonfairSync的lock方法会尝试先获取锁,如果锁获取不到才会调用acquire方法,acquire内部也会尝试再获取锁,如果获取不到加入到同步队列中循环获取锁 final void lock() { //FairSync 从AQS中继承下来的方法,下面在讲锁的获取时会进行详细的介绍 acquire(1); }
//NonfairSync重写了AbstractQueuedSynchronizer的tryAcquire模板方法,否则AQS中的tryAcquire方法会直接抛出UnsupportedOperationException异常 //tryAcquire方法在acquire中使用到,公平的获取都是基于此方法 //tryAcquire方法和NonfairSync的tryAcquire方法不同的是需要调用hasQueuedPredecessors方法,判断头节点的下一个节点的线程是否是当前线程,如果不是表明前面有等待获取锁的线程 //@param acquires 要获取的锁数 protected final boolean tryAcquire(int acquires) { //获取当前要加锁的线程 final Thread current = Thread.currentThread(); //获取锁的状态,即AQS的属性state值 int c = getState(); //如果锁的状态等于0,表示处于无锁状态 if (c == 0) { //调用从AQS继承下来的hasQueuedPredecessors方法判断同步队列是否有获取锁的节点的线程,如果是就不执行直接获取锁 if (!hasQueuedPredecessors() && //如果AQS同步队列中没有等待要获取锁的节点的线程,使用CAS更新锁的状态 compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); //返回公平的获取锁成功 return true; } } //如果当前线程是占有锁的线程 else if (current == getExclusiveOwnerThread()) { //锁的原有状态加上传入进来要获取的锁数得到新的锁状态值 int nextc = c + acquires; //如果计算出的状态值是负数,直接抛出Error错误 if (nextc < 0) throw new Error("Maximum lock count exceeded"); //设置锁的状态值为新的状态值nextc setState(nextc); //返回公平的获取锁成功 return true; } //返回公平的获取锁失败 return false; } } |
独占模式加锁机制
lock
加锁时首先使用CAS算法尝试将state状态变量设置为1,设置成功后,表示当前线程获取到了锁,然后将独占锁的拥有者设置为当前线程;
如果CAS设置不成功,则进入Acquire方法进行后续处理。
final void lock() {
// 使用CAS算法尝试将state状态变量设置为1
if (compareAndSetState(0, 1))
// 设置成功后,表示当前线程获取到了锁,然后将独占锁的拥有者设置为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 进行后续处理,会涉及到重入性、创建Node节点加入到队列尾等
acquire(1);
}
Acquire
acquire(1) 方法是AQS提供的方法
public final void acquire(int arg) { /** * 使用tryAcquire()方法,让当前线程尝试获取同步锁,获取成的话,就不会执行后面的acquireQueued() * 方法了,这是由于 && 逻辑运算符的特性决定的。 * * 如果使用tryAcquire()方法获取同步锁失败的话,就会继续执行acquireQueued()方法,它的作用是 * 一直死循环遍历同步队列,直到使addWaiter()方法创建的节点中线程获取到锁。 * * 如果acquireQueued()返回的true,这个true不是代表成功的获取到锁,而是代表当前线程是否存在 * 中断标志,如果存在的话,在获取到同步锁后,需要使用selfInterrupt()对当前线程进行中断。 */ if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
tryAcquire //NonfairSync 非公平锁中重写了AQS的tryAcquire()方法 final boolean nonfairTryAcquire(int acquires) { // 当前线程 final Thread current = Thread.currentThread(); // 获取当前state同步状态变量值,由于使用volatile修饰,单独的读写操作具有原子性 int c = getState(); // 如果状态值为0 if (c == 0) { // 使用compareAndSetState方法这个CAS算法尝试将state同步状态变量设置为1 获取同步锁 if (compareAndSetState(0, acquires)) { // 然后将独占锁的拥有者设置为当前线程 setExclusiveOwnerThread(current); return true; } } // 如果拥有独占锁的的线程是当前线程的话,表示当前线程需要重复获取锁(重入锁) else if (current == getExclusiveOwnerThread()) { // 当前同步状态state变量值加1 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); // 写入state同步状态变量值,由于使用volatile修饰,单独的读写操作具有原子性 setState(nextc); return true; } return false; }
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { /** * 通过对比源码发现,公平锁比非公平锁多了这块代码: !hasQueuedPredecessors() * hasQueuedPredecessors() 是做什么呢?就是判断当前同步队列中是否存在节点,如果存在节点呢, * 就返回true,由于前面有个 !,那么就是false,再根据 && 逻辑运算符的特性,不会继续执行了; * * tryAcquire()方法直接返回false,后面的逻辑就和非公平锁的一致了,就是创建Node节点,并将 * 节点加入到同步队列尾; 公平锁:发现当前同步队列中存在节点,有线程在自己前面已经申请可锁,那自己就得乖乖的向后面排队去。 * */ 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; } |
addWaiter
private Node addWaiter(Node mode) { // model参数是独占模式,默认为null; Node node = new Node(Thread.currentThread(), mode); // 将当前同步队列的tail尾节点的地址引用赋值给pre变量 Node pred = tail; // 如果pre不为null,说明同步队列中存在节点 if (pred != null) { // 当前节点的前驱结点指向pre尾节点 node.prev = pred; // 使用CAS算法将当前节点设置为尾节点,使用CAS保证其原子性 if (compareAndSetTail(pred, node)) { // 尾节点设置成功,将pre旧尾节点的后继结点指向新尾节点node pred.next = node; return node; } } // 如果尾节点为null,表示同步队列中还没有节点,enq()方法将当前node节点插入到队列中 enq(node); return node; } |
acquireQueued
final boolean acquireQueued(final Node node, int arg) { // 标志cancelAcquire()方法是否执行 boolean failed = true; try { // 标志是否中断,默认为false不中断 boolean interrupted = false; for (;;) { // 获取当前节点的前驱结点 final Node p = node.predecessor(); /** * 如果当前节点的前驱结点已经是同步队列的头结点了,说明了两点内容: * 1、其前驱结点已经获取到了同步锁了,并且锁还没释放 * 2、其前驱结点已经获取到了同步锁了,但是锁已经释放了 * * 然后使用tryAcquire()方法去尝试获取同步锁,如果前驱结点已经释放了锁,那么就会获取成功, * 否则同步锁获取失败,继续循环 */ if (p == head && tryAcquire(arg)) { // 将当前节点设置为同步队列的head头结点 setHead(node); // 然后将当前节点的前驱结点的后继结点置为null,帮助进行垃圾回收 p.next = null; // help GC failed = false; // 返回中断的标志 return interrupted; } /** * shouldParkAfterFailedAcquire()是对当前节点的前驱结点的状态进行判断,以及去针对各种 * 状态做出相应处理,由于文章篇幅问题,具体源码本文不做讲解;只需知道如果前驱结点p的状态为 * SIGNAL的话,就返回true。 * * parkAndCheckInterrupt()方法会使当前线程进去waiting状态,并且查看当前线程是否被中断, * interrupted() 同时会将中断标志清除。 */ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 中断标志置为true interrupted = true; } } finally { if (failed) /** * 如果for(;;)循环中出现异常,并且failed=false没有执行的话,cancelAcquire方法 * 就会将当前线程的状态置为 node.CANCELLED 已取消状态,并且将当前节点node移出 * 同步队列。 */ cancelAcquire(node); } } |
独占模式释放锁
Unlock public void unlock() { // 释放锁时,需要将state同步状态变量值进行减 1,传入参数 1 sync.release(1); } release public final boolean release(int arg) { // tryRelease方法:尝试释放锁,成功true,失败false if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) // 头结点不为空并且头结点的waitStatus不是初始化节点情况,然后唤醒此阻塞的线程 unparkSuccessor(h); return true; } return false; }
tryRelease protected final boolean tryRelease(int releases) { // 当前state状态值进行减一 int c = getState() - releases; // 如果当前独占锁的拥有者不是当前线程,则抛出 非法监视器状态 异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 更新state同步状态值 setState(c); return free; }
unparkSuccessor private void unparkSuccessor(Node node) { // 获取头结点waitStatus int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); // 获取当前节点的下一个节点 Node s = node.next; // 如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled状态的节点 if (s == null || s.waitStatus > 0) { s = null; // 就从尾部节点开始找,到队首,找到队列第一个waitStatus<0的节点。 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 如果当前节点的后继结点不为null,则将其节点中处于阻塞状态的线程unpark唤醒 if (s != null) LockSupport.unpark(s.thread); } |