JDK源码学习05-简单理解AQS和FairSync源码

JDK源码学习05-简单理解AQS和FairSync源码

1. 简介

1.1 说明

ReentrantLock的实现依托于大名鼎鼎AQS实现,AQS的注释上说了,建议使用内部工具类的方式实现AQS抽象类,作为同步工具。所以ReentrantLock内部主要有NonfairSync(非公平锁)和FairSync(公平锁)两种实现方式。
AQS就是AbstractQueuedSynchronizer即抽象队列同步器,使用模版方法设计模式,由AQS提供同步和核心算法,而把核心代码中调用的关键函数留给继承的子类来实现,这就是模版方法设计模式。DougLea建议使用实现AQS的内部类作为工具类来实现自定义的同步类。

1.2 透过FairSync理解AQS

我们可以看到ReentrantLock只不过是实现了Lock接口罢了,所以ReentrantLock的锁功能实现必然不因为Lock接口,那是怎么实现的呢?

public class ReentrantLock implements Lock, java.io.Serializable 

在这里插入图片描述

1.3 ReentrantLock的内部类

ReentrantLock内部主要有NonfairSync(非公平锁)和FairSync(公平锁)两种实现方式。
我们可以看见ReentrantLock内部主要有三个内部类。

# 由于公平锁和非公平锁有一些相似的功能,可以抽象起来
abstract static class Sync extends AbstractQueuedSynchronizer {
# 非公平锁
static final class NonfairSync extends Sync {
# 公平锁
static final class FairSync extends Sync {

2. 顺着逻辑理清FairSync脉络

2.1 根据构造函数选择公平或是非公平锁

    public ReentrantLock() {
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

2.2 上锁
接下来的流程非常重要,建议仔细阅读每一步的注释

  1. 首先我们知道lock,初始时如果获取到锁,就不会阻塞;如果初始时没有获取到锁,就会阻塞
  2. 表面上,上面一句话是废话,但是在追读源码的时候很容易忽视
  3. 清楚上面之后,下面的acquire函数的功能就非常容易解释了,获取到锁就放行,把它当作一个空函数;没获取到锁就阻塞,一直阻塞到获取到锁,然后放行。(这就是锁的究极含义)
    public void lock() {
        sync.lock();
    }
    // FairSync中的函数
    final void lock() {
        acquire(1);// AQS函数
    }
    // AQS中的函数   接下来就是重点了  我们仔细追踪每一步流程
    // - 
    public final void acquire(int arg) {
    	// 4. 先使用tryAcquire尝试获取锁,如果获取到,if条件整个为false,就可以直接放行了,就很棒。
    	// 5. 如果tryAcquire没有获取到锁,就会执行后面的条件了,首先执行的是addWaiter函数,addWaiter函数
    	//    的参数只是用来标记状态的,不需要关心。现在先执行addWaiter函数。
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  1. 现在我们细看上面的步骤,先使用tryAcquire尝试获取锁,如果获取到,if条件整个为false,就可以直接放行了,就很棒。tryAcquire是AQS提供给我们实现的方法,用来实现自己的锁逻辑,我们先简略看看即可,不必深究。下面代码的逻辑非常复杂,特别是hasQueuedPredecessors中有很多种情况,我们留着后面再解析(这不过是实现公平锁特有的代码段,而我们更希望研究调用这个tryAcquire函数的框架AQS----模版方法设计模式)
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
        	// hasQueuedPredecessors的英文含义是已经给队列排队前置节点
        	// 在队列不是空的情况下 如果当前线程是第一个排队的节点(即链表第二个节点) 函数返回true否则false
            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;
    }
    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
  1. 如果tryAcquire没有获取到锁,就会执行后面的条件了,首先执行的是addWaiter函数,addWaiter函数的参数只是用来标记状态的,不需要关心。现在先执行addWaiter函数,addWaiter的意思就是把当前线程添加到同步队列的尾部。
	private Node addWaiter(Node mode) {
        // 获取锁失败的线程会执行到这里 并且自己把自己入队
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        // 如果tail不为null  先尝试在本函数入队  失败就使用enq循环入队 鬼知道为什么要这多余的一步
        if (pred != null) {
            // 进队操作  参考enq函数的else
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 函数就在下方
        enq(node);
        return node;
    }
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            // 只有在第一次进队才出现t为null的情况  
            // 初始队头--》象征获取锁的线程 但不是  队头node中thread永远为null
            // 初始化第一次的头节点之后  就可以真正进队当前节点了(第二次for循环)
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 一直到进队成功  才结束函数
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
  1. 上面的代码看起来很长,其实也就是 死循环+CAS 将当前线程加入队尾罢了。
# 唯一需要注意的是,AQS中的头节点是懒加载的形式,阻塞的节点只能从第二个节点开始排队。
  1. 此时我们再次回到第三步,及下述代码,此时我们可以将条件简化了。我们假设有人先获取了锁,然后我才来,此时tryAcquire返回false,所以现在我们来研究以下acquireQueued函数
if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
if (acquireQueued(new Node(“我是进来排队的线程”), arg))
            selfInterrupt();
// 死循环!!  此处的node是上一部中被添加到AQS尾部的节点
// 此时是tryAcquire失败后 第一次尝试再次尝试获取锁(由于是公平锁 则必须是第一个排队的人)
// 如果获取失败,就会进入下一个判断
// shouldParkAfterFailedAcquire我们简要说明一下 
//虽然还包含了取消节点的功能  但这里不扩展了 只是为了方便理解的说明
// 只有在前一个节点状态是-1的时候 才会返回true   如果前一个节点状态不为-1  则设置为-1
//       第一次节点状态默认是0  shouldParkAfterFailedAcquire检查之后设为-1
// 故循环一般会被循环一次  两次  在第两次的时候  才会调用parkAndCheckInterrupt方法  进入阻塞
// 如果我这个线程被唤醒  很明显 通过下面逻辑  本函数返回的一定是false
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 死循环!!  此处的node是上一部中被添加到AQS尾部的节点
            // 此时是tryAcquire失败后 第一次尝试再次尝试获取锁(由于是公平锁 则必须是第一个排队的人)
            // 如果获取失败,就会进入下一个判断
            // shouldParkAfterFailedAcquire我们简要说明一下 
            //虽然还包含了取消节点的功能  但这里不扩展了 只是为了方便理解的说明
            // 只有在前一个节点状态是-1的时候 才会返回true   如果前一个节点状态不为-1  则设置为-1
            //       第一次节点状态默认是0  shouldParkAfterFailedAcquire检查之后设为-1
            // 故循环一般会被循环一次  两次  在第两次的时候  才会调用parkAndCheckInterrupt方法  进入阻塞
            // 如果我这个线程被唤醒  很明显 通过下面逻辑  本函数返回的一定是false
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
	// 这个函数不是实际源码  而是我删减后的
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
  1. 现在我们基本分析完成了整个AQS的流程

3. 暂未写完,留待更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值