源码分析AQS独占锁、共享锁和Condition的实现

AbstractQueuedSynchronizerjava.util.concurrent包下非常重要和基础的类,concurrent包下提供了一系列的同步操作需要的工具,包括了ReentrantLockReentrantReadWriteLockThreadPoolExecutorCountDownLatchLinkedBlockingQueue等等,该包下很多的工具类都直接或者间接基于AQS提供的独占锁、共享锁和等待队列实现了各自的同步需求。

一、AQS的设计:①AQS将资源抽象成一个int型的值:int state,通过对state的修改,达到对AQS不同状态的控制,对state变量的修改由其子类实现;②并不是state>0才代表有资源可以申请,如ReentrantLock中只有state==0的时候才获取成功,state>0代表资源被占用,什么情况代表有资源可用是子类根据state的值做的一个抽象而已;③AQS的两个方法:acquire(int arg)、release(int arg),分别用来获取和释放一定量的资源,即增大和减小state的值。当线程执行上述两个方法时,AQS的子类尝试修改state的值;④在acquire()方法中:若state大小符合特定需求(具体逻辑由子类实现),则线程会锁定同步器,否则将当前线程加入到同步队列中;在release()方法中:若state大小符合特定需求,则释放掉当前线程占有的资源,唤醒同步队列中的线程。

二、通过ReentrantLock的源码分析独占锁:ReentrantLock中对state的设计为:state==0的时候代表没有线程持有同步器,在state>0的时候,其它线程是不能获取同步器的,必须加入到同步队列中等待。
1、从lock()方法开始:这里只分析NonfairSync

static final class NonfairSync extends Sync {
		//从这个方法开始申请锁
        final void lock() {
        	//当前state的值为 0,线程直接获取到锁,setExclusiveOwnerThread()方法可以设置当前持有锁的线程
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
            	//说明已经有线程持有了锁,则acquire()申请资源,acquire()函数的作用就是:
            	//首先调用tryAcquire(),尝试获取资源,在此的具体实现就是nonfairTryAcquire()方法
            	//获取资源失败则将线程加入到同步队列中等待
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();
        //尝试获取资源的地方
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //首先还是判断是否有线程已经持有锁(c=0),还未有线程持有锁则让该线程持有锁,设置state的值为acquires,返回true
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //已经有线程持有锁,判断持有锁的线程和申请锁的线程是否是同一个线程,如果是,让state值增加acquires,返回true
            //也就是同一个线程可以重复获取到同步器,从这里可以看出为什么ReentrantLock锁是可重入的了
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            //不是上面两种情况,即获取资源失败,返回false
            return false;
		//判断获取到锁的是不是当前线程
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
        	//获取Condition实例,ConditionObject的定义在AQS中
            return new ConditionObject();
        }

        final Thread getOwner() {
        	//获取当前持有锁的线程,state值为 0,代表还未有线程持有锁
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
		//state不为0,就代表有线程持有锁了
        final boolean isLocked() {
            return getState() != 0;
        }
    }

2、Node类:是同步队列中节点的描述,用来保存等待的线程、状态、前驱和后继节点等的信息。

static final class Node {
        //标识共享模式的节点,共享模式下Node节点的nextWaiter变量设置为这个值
        static final Node SHARED = new Node();
        //标识独占模式的节点,独占模式nextWaiter变量是null
        static final Node EXCLUSIVE = null;
        //同步队列中被取消的节点,被中断或者等待超时,该状态的节点不再被使用
        static final int CANCELLED =  1;
        //标识后继节点处于被唤醒的状态,当节点释放同步器后,会唤醒后继节点中第一个处于该状态的节点
        static final int SIGNAL    = -1;
        //描述处于等待队列中的节点,节点中的线程等待在Condition上,
        //在调用signal()之后,被唤醒的节点将从等待队列中转移到同步队列中继续等待
        static final int CONDITION = -2;
        //确保共享模式下可以唤醒后续的共享节点
        static final int PROPAGATE = -3;
        //保存节点状态,值为 0、CANCELLED、SIGNAL、CONDITION、PROPAGATE      
        volatile int waitStatus;
        
        //链表的上一个节点
        volatile Node prev;

        //链表的下一个节点
        volatile Node next;

        //保存被阻塞的线程
        volatile Thread thread;

        //用来保存某个Condition上的等待队列,也用来判断节点是否是共享节点
        Node nextWaiter;

        //判断节点是否是共享模式,通过判断nextWaiter==SHARED
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        //找到前驱节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {}

        Node(Node nextWaiter) {
            this.nextWaiter = nextWaiter;
            U.putObject(this, THREAD, Thread.currentThread());
        }

        Node(int waitStatus) {
            U.putInt(this, WAITSTATUS, waitStatus);
            U.putObject(this, THREAD, Thread.currentThread());
        }

        /** CAS设置节点的状态 */
        final boolean compareAndSetWaitStatus(int expect, int update) {
            return U.compareAndSwapInt(this, WAITSTATUS, expect, update);
        }

        /** CAS设置后继节点 */
        final boolean compareAndSetNext(Node expect, Node update) {
            return U.compareAndSwapObject(this, NEXT, expect, update);
        }
		//Unsafe 是CAS的工具类,CAS是虚拟机实现的原子性操作
        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        private static final long NEXT;
        static final long PREV;
        private static final long THREAD;
        private static final long WAITSTATUS;
        static {
            try {
            	//分别拿到Node节点的next、prev、thread、waitStatus变量的句柄,CAS通过句柄修改这些变量
                NEXT = U.objectFieldOffset
                    (Node.class.getDeclaredField("next"));
                PREV = U.objectFieldOffset
                    (Node.class.getDeclaredField("prev"));
                THREAD = U.objectFieldOffset
                    (Node.class.getDeclaredField("thread"));
                WAITSTATUS = U.objectFieldOffset
                    (Node.class.getDeclaredField("waitStatus"));
            } catch (ReflectiveOperationException e) {
                throw new Error(e);
            }
        }
    }

3、AQS的acquire(int arg)方法:从NonfairSync 的实现看出,申请锁(资源)的时候,首先会执行acquire()方法。
acquire()方法,尝试获取资源,失败将线程加入同步队列中

   public final void acquire(int arg) {
	//tryAcquire()尝试获取资源,返回false,执行acquireQueued()方法,将线程加入同步队列中,让线程进入等待状态
	//addWaiter()这里传入的是独占标志(Node.EXCLUSIVE),说明该节点是一个独占节点
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

addWaiter()方法,将当前线程封装进Node节点,并将节点加入到同步队列中,如果头结点为null,则初始化头结点和尾节点,并将当前节点链接在尾节点之后。

 private Node addWaiter(Node mode) {
    	//初始化一个Node节点,变量nextWaiter为mode
        Node node = new Node(mode);
        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
            	//将当前节点设置成尾节点,链接在之前的尾节点之后
                U.putObject(node, Node.PREV, oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
            	//初始化头节点和尾节点指向同一个节点
                initializeSyncQueue();
            }
        }
    }

acquireQueued()方法,自旋直到线程持有同步器,这里线程将进入等待状态,直到其它线程释放资源,唤醒处于队首的线程,唤醒后该线程必须要继续获取到资源

final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            //自旋,直到线程持有同步器,(tryAcquire()成功)
            for (;;) {
                final Node p = node.predecessor();
                //该节点的前驱是头节点,说明唤醒顺序先进先出的
                //尝试获取资源,成功则返回interrupted,让该线程持有同步器
                if (p == head && tryAcquire(arg)) {
                    setHead(node);	//设置节点为头节点
                    p.next = null; // 帮助释放内存
                    return interrupted;
                }
                //检查节点状态,如果可以进入等待状态,则让线程进入等待状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

shouldParkAfterFailedAcquire()方法,设置节点的状态,要为node节点找到一个符合需求的前驱节点,并设置其状态为SIGNAL,表明node节点是一个在等待状态的正常的节点

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //前驱节点的waitStatus为SIGNAL,说明后继节点可以进入等待状态
        if (ws == Node.SIGNAL)          
            return true;
        if (ws > 0) {
        	//前驱节点是CANCELLED,则跳过这些节点
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        	//前驱节点状态正常的情况下,设置状态为SIGNAL,标志后继节点可以进入等待状态
            pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
        }
        return false;
    }

parkAndCheckInterrupt()方法:借助LockSupport将当前线程阻塞,在唤醒之后返回中断状态,因为在阻塞状态下,线程不响应中断,在唤醒之后,如果有需要则重新处理该中断。

 private final boolean parkAndCheckInterrupt() {
	//LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程
	//其它说明可百度
        LockSupport.park(this);
        return Thread.interrupted();
    }

4、AQS的release(int arg)方法:acquire()方法完成了资源获取成功后持有同步器,获取失败后加入到同步队列中,并将线程阻塞等一系列操作。下面分析同步队列线程被唤醒的过程。
ReentrantLockunlock()方法只调用了release(1)方法:tryRelease()由子类实现,用来判断是否可以唤醒线程。如果可以则调用unparkSuccessor()唤醒一个线程

 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            //这里传入的是头结点,也就是说唤醒的是头结点的后继节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

ReentrantLocktryRelease()方法:

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //state==0才能唤醒同步队列中的线程
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            //重新设置state的值
            setState(c);
            return free;
        }

unparkSuccessor(Node node)方法:

private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);
	//找到需要被唤醒的节点
        Node s = node.next;
        //如果当前节点的后继节点不符合要求(为null或者被取消了),则从尾节点向前查找
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        //唤醒该线程,线程的阻塞是在acquireQueued()方法中,线程将从阻塞的地方继续执行
        if (s != null)
            LockSupport.unpark(s.thread);
    }

三、通过CountDownLatch分析共享锁:CountDownLatch在释放同步器之后会一次唤醒所有等待在同步队列上的线程。主要功能是:允许一个或者多个线程等待其他线程完成任务,然后这些等待的线程同时被唤醒,使用很简单,下面主要分析共享锁的实现,在ReentrantLock中分析过的函数不再分析。
1、CountDownLatch中的自定义同步器:

//CountDownLatch的构造函数,传入了一个int值,作为state的初始值
public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
private static final class Sync extends AbstractQueuedSynchronizer {
        Sync(int count) {
	//设置state的初始值为自己传入的参数
            setState(count);
        }

        int getCount() {
            return getState();
        }
	//只有state为 0的时候,也就是其它线程全部执行完成后,才算获取资源成功,返回1
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
        	//只有在state==1的时候才会返回true,进而释放同步器,唤醒等待的线程
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c - 1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

2、CountDownLatch中的await()方法:获取资源,只调用了AQS的acquireSharedInterruptibly()方法,该方法会抛出中断异常,在调用await()时要捕获异常。

 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //尝试获取资源,返回int型值,tryAcquire()返回bool值
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

3、doAcquireSharedInterruptibly()方法:

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //将一个共享节点添加到同步队列中
        final Node node = addWaiter(Node.SHARED);
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    //尝试获取资源成功后(r>=0),setHeadAndPropagate()将head节点指向node,然后唤醒同步队列中的共享节点
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        return;
                    }
                }
                //修改节点的状态,阻塞线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //如果等待过程中产生了中断,则抛出中断异常
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

4、setHeadAndPropagate()方法:设置头结点为node节点,并尝试唤醒后续的共享节点

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; 
        setHead(node);
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //是共享节点尝试释放资源
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

5、countDown()方法释放资源:该方法中只调用了releaseShared(1)方法。

  public final boolean releaseShared(int arg) {
	//尝试释放指定量的资源,成功之后,doReleaseShared()唤醒同步队列中的线程
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

6、doReleaseShared()方法:唤醒后续的节点,

private void doReleaseShared() {
	//自旋确保在CAS失败的情况下可以再次执行
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                	//头结点的后继节点在等待状态,则唤醒后继节点
                    if (!h.compareAndSetWaitStatus(Node.SIGNAL, 0))
                        continue;            
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !h.compareAndSetWaitStatus(0, Node.PROPAGATE))
                    continue;                
            }
            if (h == head)                   
                break;
        }
    }

总结:doAcquireShared()请求资源,失败则将一个共享节点加入到同步队列, releaseShared()方法释放资源,成功后setHeadAndPropagate()方法中将唤醒后续的共享节点。

四、简单通过LinkedBlockingQueue分析Condition的实现:BlockingQueue接口是做等待队列用的,在线程池中就有过使用(ThreadPoolExecutor的使用及源码分析)。借助Condition实现了生产-消费者模式,生产消费线程将阻塞在Condition队列上面,
1、通过take()方法分析阻塞和唤醒线程的过程:

    //新建ReentrantLock 锁
    private final ReentrantLock takeLock = new ReentrantLock();

    //新建Condition 实例,是通过ReentrantLock的newCondition()方法
    private final Condition notEmpty = takeLock.newCondition();
    
    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        //在执行await()或者signal()方法前必须先获取锁,在后面的分析中会看到原因
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {
            	//将线程加入到notEmpty等待队列中
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
            	//从notEmpty等待队列中唤醒一个线程
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }

2、await()方法:将线程加入的某个Condition的等待队列上,然后让线程进入等待状态,等待signal()的唤醒,然后被移入到同步队列。

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //新建Node节点添加到等待队列中
            Node node = addConditionWaiter();
            //当线程进入等待队列的时候,会释放之前申请的资源,这时候会唤醒同步队列上的其它线程
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //节点不在同步队列上,则阻塞该线程,等待signal()方法的唤醒,
            //被唤醒后,节点将从等待队列中被转移到同步队列中,然后跳出这个循环
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //线程被唤醒之后,要去申请资源,该方法之前分析过,只有申请到资源才能唤醒线程,否则会被再次阻塞
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) 
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
//释放所有资源
final int fullyRelease(Node node) {
        try {
            int savedState = getState();
            //释放资源,从这里可以知道,在没有持有同步器的情况下,执行await()方法会报IllegalMonitorStateException错误
            if (release(savedState))
                return savedState;
            throw new IllegalMonitorStateException();
        } catch (Throwable t) {
            node.waitStatus = Node.CANCELLED;
            throw t;
        }
    }

3、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);
        }

4、transferForSignal()方法:将等待队列中的一个节点添加到同步队列的队尾,并唤醒该线程,让线程进入到acquireQueued()方法自旋申请资源。到这里signal()方法执行完成,在takeLock.unlock()之后,会重新从同步队列中唤醒队头节点,这样就完成了一次等待–唤醒的流程。

final boolean transferForSignal(Node node) {
        //将节点的CONDITION状态设置为0(初始状态)
        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
            return false;
        //将等待队列的节点放入到队尾,返回的是之前的队尾节点
        Node p = enq(node);
        int ws = p.waitStatus;
        //设置之前队尾节点状态为SIGNAL,然后唤醒等待队列中的线程,node节点进入acquireQueued()自旋
        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

总结:await()会将线程阻塞,节点放入到等待队列中,signal()会将等待队列的首节点移到同步队列中,会唤醒一次该节点,该节点进入到acquireQueued()自旋,在释放锁之后,会再次释放资源,唤醒同步队列中的节点。

猜你喜欢

转载自blog.csdn.net/weixin_38062353/article/details/83177998