JDK1.8源码解析之ReentrantLock(可重入锁)

ReentrantLock源码解析

  • 前言
    在学习阻塞队源码的时候,比如ArrayBlockingQueue、LinkedBlockingQueue、CyclicBarrier、SynchronousQueue频繁的遇到ReentrantLock,故先把ReentrantLock原理了解,是学习阻塞队列的基础。对Doug Lea大师崇拜。

  • 类继承关系
    在这里插入图片描述
    有一个特别重要的抽象静态内部类Sync,这个类实现了AbstractQueueSynchronizer(提供锁模板,这篇文章中用到时候在分析)。

  • ReentrantLock有两种模式,1 公平锁 2 公平锁

public ReentrantLock(boolean fair) {
	 sync = fair ? new FairSync() : new NonfairSync();// 初始化的时候可以指定,不指定话默认是非公平模式
}

非公平锁

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    final void lock() {// 重写Sync的lock方法,自定义非公平锁的实现逻辑,父类相当与模板,模板实现了大部分的方法,子类只需实现自己的逻辑。
        if (compareAndSetState(0, 1)) // 这里是体现非公平锁的关键,申请锁的时候先直接尝试修改AQS的锁标志(非常的强硬)
            setExclusiveOwnerThread(Thread.currentThread());// 成功修改锁标志位 则设置当前独占锁的线程
        else
            acquire(1);// 这里走的是AQS的模板方法 尝试获取
    }

    protected final boolean tryAcquire(int acquires) { // 尝试获取锁  NonfairSync类定义
        return nonfairTryAcquire(acquires);
    }
}    

来看看AQS的acquire

public final void acquire(int arg) {
        if (!tryAcquire(arg) && // 执行子类自定义的方法
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))// 申请失败情况下 将当前线程添加到对列中
            selfInterrupt();
    }

来看看NonfaireSync的tryAcquire

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState(); // 获取AQS锁状态
        if (c == 0) {// 当前锁处于空闲状态
            if (compareAndSetState(0, acquires)) {// unsafe设置锁状态
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {// 这里体现了ReentrantLock可重入的性质,同一个线程可以多次获取锁
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;// 尝试获取锁失败
}

所获取失败,则会将当前线程加入到阻塞队列,来看看AQS的acquireQueued 是如何实现的?

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)) {// 等待队列的head释放了锁
                    setHead(node);// 将当前节点设置成head
                    p.next = null; // help GC // 自己的next指向自己方便gc回收
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&// 获取锁失败后开始进入真正的阻塞
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

来看看AQS的shouldParkAfterFailedAcquire是如何实现的?

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		// 只有当前节点的前驱节点的状态是signal的才可以parking,就是告诉前驱我准备好了,你执行完记得唤醒我
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;// 跳过被取消的前驱节点,找到一个离自己最近的非取消的节点
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
}

接下来再看看parkAndCheckInterrupt 这个方法就是用unsafe的park,使当前线程阻塞,等待unpark。
到此整个获取锁的过程结束了,我的天感觉脑子不够用了。
既然这里park了,那在哪里unpark这个线程呢?来看看ReentrantLock 的unlock,所以在使用lock的地方必须要以unlock结束,lock几次就需要unlock几次,将AQS的state恢复至0,要不然等待的线程永远得不到唤醒。

public void unlock() {
        sync.release(1);
    }
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0) // h不等于null 说明等待队列不为空
                unparkSuccessor(h);// 唤醒后继节点
            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;// 直到AQS状态为0,才算完成锁的释放
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

公平锁
了解了非公平锁,公平锁就超级简单了,唯一不同的就是tryAcquire了,来看看?

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;
        }

ReentrantLock还有一个强大的特性,一个lock对象可以创建多个条件,每个条件也是可以阻塞的,这样就使ReentrantLock更加灵活,而Synchronized只能对一个类或者一个对象加锁。下面来看看Condition。
Condition是一个接口
在这里插入图片描述
总共只有两类方法,await类的方法是将任务阻塞到条件队列中,signal方法是将任务从条件队列中转移到lock的等待队列中。
AQS中的内部类ConditionObject实现了Condition接口,先看成员变量,

/**条件队列的第一个等待线程*/
private transient Node firstWaiter;
/** 条件队列的最后一个等待线程 */
private transient Node lastWaiter;

条件队列就是一个单向链表,链表中的节点用的依旧是AQS中的节点,创建新节点时,将新节点设置为lastWaiter的next,完成链接。

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();// 将任务添加到条件队列的尾部
            int savedState = fullyRelease(node);// 这里将AQS锁的状态清除掉,savedState是保存await之前node的状态,因为在唤醒之后需要执行unlock操作,这里也体现了可重入锁,
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {// 只有await获得signal后才会将node移动到lock的等待队列中。
                LockSupport.park(this);// 执行阻塞,等待signal 的transfer
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)// 走到这里就说明node已经从条件队列中转移到等待队列中,现在可以尝试获取lock锁
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
}

接下来看看AQS ConditionObject 的signal,signal的目的是将条件队里中的头结点转移动等待队列中。

public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
}
private void doSignal(Node first) {
    do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) && (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        Node p = enq(node);// 把任务添加到等待队列
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            	LockSupport.unpark(node.thread);// 唤醒阻塞线程,回到await方法,继续执行,接下来进行尝试获取lock锁,这里就和等待队列中尝试获取锁一样了。
        return true;
}

condition这块核心的部分分析完了,其他的方法,基本上没啥难点了,下一篇开启阻塞队列之旅了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值