简单纪要:浅谈ReentrantLock的父亲AbstractQueuedSynchronizer(AQS)的底层实现

在日常开发中,我们都直接或间接的使用过ReentrantLock,以及他的兄弟们Semaphore,CountdownLatch,ReadwriteLock,但是对于其底层是如果实现锁机制的呢?

下面我们先来看一张图:

   

大家可能在想,这张图里面并没有看到我们知道ReentrantLock,甚至连Lock我们也没有看到,不急,我可以先new一个Lock来看看:

 Lock lock = new ReentrantLock();  
/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

现在想必大家都明白了,我们在创建ReentrantLock的对象的时候 ,其实是创建了一个NonfairSync()对象,NonfairSync类是静态内部类,他继承了Sync,Sync继承了我们今天的主角AbstractQueuedSynchronizer;

现在我们先不急于看源码,我可以先看一张图,毕竟图是最直观的,走起,看图~

AQS是将每一条请求共享资源的线程封装为一个CLH锁队列的节点,AQS就是基于CLH实现的,内部维护了一个volatile int state 字段,线程通过CAS修改同步资源状态,成功则获取锁成功,失败则进入等待队列,等待唤醒;

volatile int state 字段可以通过三种方式操作:

  • getState();
  • setState();
  • compareAndSetState();

AQS实现了两种资源共享的方式:独占(Exclusive)和共享(Share);

 1. 从acquire(int arg)开始调取AQS中的程序,调取tryAcquire()方法获取同步资源(state)的状态,判断当前锁state>=0才可以获取到锁,获取一次,计数器进行 + 1 ,释放一次 -1 ;

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

 2. 如果tryAcquire()获取同步资源state 小于0 ,返回失败;调取addWaiter(Node)方法,将当前线程放入等待队列的队尾,并返回当前队列;

将当前线程包装为Node,执行入队操作,将当前节点插入到等待队列的队尾;成功,返回当前线程包装为的node;

失败,调取enq(Node)方法;

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;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

3. 失败,调取enq(Node)方法,此方法  自旋 直到成功直接使用CAS操作 ,只能返回一个节点;

首先判断尾节点是否为空,就是判断队列是否初始化,因为 队列必须初始化, 当队列未初始化,创建一个空的标志结点作为head结点,并将tail也指向head;

如果队列初始化成功,使用CAS保证只有一个头节点初始化成功;

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

4. addWaiter()方法执行完毕后,节点已经被加入到等待队列当中,当前线程处于挂起状态,等待被唤醒;调用acquireQueued()方法,返回获取资源的状态;

获取当前节点的前驱节点是  头节点,并尝试获取同步资源的状态成功,调用setHead()将当前节点设为头节点,setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了!

如果当前节点不是头节点,执行 shouldParkAfterFailedAcquire(p, node)方法,

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; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }

如果当前节点不是头节点,执行 shouldParkAfterFailedAcquire(p, node)方法处理获取锁失败的挂起逻辑,获取前驱的状态pred.waitStatus,如果已经设置如果前驱节点拿到资源后告诉的自己,即已经设置Node.SIGNAL,则可以进入等待状态,如果前驱节点已经取消,就跳过当前的前驱节点,到找到最近一个正常等待的状态,并排在它的后边,则之前的前驱节点无引用,则会被GC回收。

如果前驱节点状态正常,就设置前驱节点的状态为SINGAL,获取到资源后通知;

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        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;
    }

 如果shouldParkAfterFailedAcquire()方法返回ture,证明前驱节点的状态已经设置好,调用parkAndCheckInterrupt()方法; 

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);// 调用park(),线程进入waiting状态
        return Thread.interrupted();//返回线程中断状态
    }

此时,AQS已经大体看完了,此时再看看我们开篇提供的流程图,想必大家理解的会更加深刻,希望能对大家有所帮助,有问题请指出哦^_^~

 

猜你喜欢

转载自www.cnblogs.com/Rnan/p/11962259.html