AQS与应用

AQS的实现基于如下三个

  1. volatile,保证了可见性与防止指令重排,正常加锁都是需要修改state的值,volatile写不会和前面操作重排序,第二个线程也是volatile写,两个volatile写之间不会重排序,这样第一个线程所有操作第二个线程都能感知到
  2. CAS 原子的更新state值,如果能成功更新,可以表示加锁成功
  3. LockSupport, 让线程暂停与恢复

LockSupport

先看下LockSupport中park,unpark以及线程中断的伪代码

park(){
    if(permit==1){
        permit=0;
        return;
    }
    if(thread.isInterrupted() == true){
        return;
    }
    阻塞线程
    permit=0;
}

unpark(){
	if(permit<1){
        permit=1;
        if(线程被阻塞){
            唤醒线程
        }
    }
}
interrupt(){
	if(中断状态==false){
        中断状态=true;
    }
    unpark()
}

LockSupport支持先执行unpark,再执行park这样也是不会阻塞的,park会去判断是否permit=1,是的话直接返回.
以前只以为中断操作只是给中断变量做一个赋值操作,实际上会去做唤醒线程的操作.

AQS源码

数据结构

AQS主要的数据结构是一个CLH链表,链表中结构如下

volatile Node prev;       // initially attached via casTail
volatile Node next;       // visibly nonnull when signallable
Thread waiter;            // visibly nonnull when enqueued
volatile int status;      // written by owner, atomic bit ops by others

waiter记录的是当前线程,status记录当前node节点的状态.
CLH队列有head和tail两个节点,tail节点就是正常加入到队列中的节点,head节点其实也是正常加入到队列中的节点,只是就算CLH队列为空,也会存在一个head节点,所以实际上一个节点判断出它的上一个节点就是head节点,就可以认为它已经在队首了,可以尝试调用tryAcquire获取锁.

state变量

AQS中提供了一个volatile int state变量,初始时候state=0,所有继承AQS的类通过state变量来实现自己的需求,判断是否是加锁状态之类的,比如reentrantlock中state=0表示未加锁状态,state>0表示加锁状态,且由于是可重入锁,state=x表示当前线程重入锁x次. 其他像countdownLatch等如何利用state后面遇到了再讲.
AQS提供的操纵state方法中只有get,set,以及compareAndSetState,通过compareAndSetState就能实现原子的比较state原始值并赋予新值操作,像是reentrantlock就是通过此方法来修改state的值,如果能成功的将state由0改成1,证明获取锁成功.

acquire方法

AQS最重要的就是acquire方法了,下面详细讲解下

final int acquire(Node node, int arg, boolean shared,
                  boolean interruptible, boolean timed, long time) {
    Thread current = Thread.currentThread();
    byte spins = 0, postSpins = 0;   // retries upon unpark of first thread
    boolean interrupted = false, first = false;
    Node pred = null;               // predecessor of node when enqueued

    //上面几个字段先解释下
    //spins是在当前节点已经是head节点下一个我们称为头结点时候,由于非公平锁,所以即使是头节点
    //也不能保证一定能获取到锁,当未获取到锁时,循环spins次数尝试获取锁,未获取到才会暂停线程
    //postSpins,配合spins记录上一次暂停线程spins次数,此次线程被唤醒后spins次数为
    //postSpins*2+1
    //first表示当前node是否为头结点
    //pred表示当前节点的上一节点
    
    /*
     * Repeatedly:
     *  Check if node now first
     *    if so, ensure head stable, else ensure valid predecessor
     *  if node is first or not yet enqueued, try acquiring
     *  else if queue is not initialized, do so by attaching new header node
     *     resort to spinwait on OOME trying to create node
     *  else if node not yet created, create it
     *     resort to spinwait on OOME trying to create node
     *  else if not yet enqueued, try once to enqueue
     *  else if woken from park, retry (up to postSpins times)
     *  else if WAITING status not set, set and retry
     *  else park and clear WAITING status, and check cancellation
     */

    for (;;) { 
        // 判断当前节点不是头节点且当前节点加入到CLH队列中
        if (!first && (pred = (node == null) ? null : node.prev) != null &&
            !(first = (head == pred))) {
            //前驱节点被取消了,从队列中移除前驱节点
            if (pred.status < 0) {
                //将队列中状态为cancel的节点移除出队列
                cleanQueue();           // predecessor cancelled
                continue;
            } else if (pred.prev == null) {
                Thread.onSpinWait();    // ensure serialization
                continue;
            }
        }
        // 当前节点是头结点 | 当前节点还未创建 | 当前节点还未加入到CLH队列中
        // 这种情况可以尝试获取锁
        if (first || pred == null) {
            boolean acquired;
            try {
                if (shared)
                    tryAcquireShared方法是由上层继承AQS的类去实现
                    acquired = (tryAcquireShared(arg) >= 0);
                else
                    //tryAcquire方法是由上层继承AQS的类去实现
                    acquired = tryAcquire(arg);
            } catch (Throwable ex) {
                cancelAcquire(node, interrupted, false);
                throw ex;
            }
            if (acquired) {
                if (first) {
                    node.prev = null;
                    head = node;
                    pred.next = null;
                    node.waiter = null;
                    if (shared)
                        //share模式的话判断node下一个节点是否是share节点,是的话唤醒下一个节点
                        signalNextIfShared(node);
                    if (interrupted)
                        current.interrupt();
                }
                return 1;
            }
        }
        Node t;
        //tail还不存在表明CLH队列从未创建过,初始化一个节点,head和tail都指向它
        if ((t = tail) == null) {           // initialize queue
            if (tryInitializeHead() == null)
                return acquireOnOOME(shared, arg);
        //当前节点为空的话创建当前节点
        } else if (node == null) {          // allocate; retry before enqueue
            try {
                node = (shared) ? new SharedNode() : new ExclusiveNode();
            } catch (OutOfMemoryError oome) {
                return acquireOnOOME(shared, arg);
            }
        //当前节点加入CLH队列
        } else if (pred == null) {          // try to enqueue
            node.waiter = current;
            node.setPrevRelaxed(t);         // avoid unnecessary fence
            if (!casTail(t, node))
                node.setPrevRelaxed(null);  // back out
            else
                t.next = node;
        // 当是头结点且spins自旋次数>0时,只睡眠很短时间,又进入上面tryAcquire逻辑
        } else if (first && spins != 0) {
            --spins;                        // reduce unfairness on rewaits
            Thread.onSpinWait();
        //更改当前node状态为waiting,并再次回到循环头进行检查
        } else if (node.status == 0) {
            node.status = WAITING;          // enable signal and recheck
        } else {
            long nanos;
            // 指定当是头结点时自旋次数为上一次park前自旋次数*2+1,初始为1,
            spins = postSpins = (byte)((postSpins << 1) | 1);
            if (!timed)
                LockSupport.park(this);
            //指定获取锁等待时间的当到达时间后,下面逻辑会清除状态,如果已经是头结点,会去尝试
            //获取锁,不是头结点重新设置状态waiting,再去判断是否是头结点,当还不是时才会取消
            //获取锁
            else if ((nanos = time - System.nanoTime()) > 0L)
                LockSupport.parkNanos(this, nanos);
            else
                break;
            node.clearStatus();
            //如果当前线程被中断过且acquire方法支持中断的话退出循环
            if ((interrupted |= Thread.interrupted()) && interruptible)
                break;
        }
    }
    return cancelAcquire(node, interrupted, interruptible);
}

流程如下,先翻一下acquire方法中的注释

  循环
* Repeatedly:
   检查node是否是头结点
*  Check if node now first
     是的话,确保head节点稳定
     不是的话,确保前驱是有效的,即执行cleanQueue操作,删除队列中cancel的节点
*    if so, ensure head stable, else ensure valid predecessor
   如果node是头结点或者还未入队,尝试获取锁
*  if node is first or not yet enqueued, try acquiring
   如果队列还未初始化,初始化节点并赋值给head和tail
*  else if queue is not initialized, do so by attaching new header node
*     resort to spinwait on OOME trying to create node
   如果当前node未创建,创建它
*  else if node not yet created, create it
*     resort to spinwait on OOME trying to create node
   
*  else if not yet enqueued, try once to enqueue
*  else if woken from park, retry (up to postSpins times)
*  else if WAITING status not set, set and retry
*  else park and clear WAITING status, and check cancellation

AQS流程

  1. 未加入队列或者是队列中头结点,执行tryAcquire或者tryAcquireShared
    1. 成功tryAcquireShared的话,判断下一个节点是否是shared节点以及状态是waiting,是的话执行unpark唤醒,这里是链式唤醒,当前节点只会唤醒下一个节点,写一个节点如果成功tryAcquireShared,下一个节点会去唤醒下下个节点
  2. 判断当前node是否为null,是的话初始化
  3. 判断当前node是否加入队列,未加入的话加入
  4. 判断当前node status==0,是的话设置status=waiting
  5. 执行park,增加下次自旋次数spins,park醒来后检查中断状态是否应该退出循环

node status状态说明

node状态有四个,初始化node节点为0,waiting=1,cond=2,CANCELLED = 0x80000000为一个负数.
cond和cancelled状态非常好理解,主要讲下初始化状态以及waiting状态.
waiting状态表示当前节点处于一个可以被其他线程执行unpark唤醒的状态,可以看下signalNext函数

private static void signalNext(Node h) {
    Node s;
    if (h != null && (s = h.next) != null && s.status != 0) {
        s.getAndUnsetStatus(WAITING);
        LockSupport.unpark(s.waiter);
    }
}

head节点在唤醒下一个节点时候会去判断状态是否为0,不为0的才会唤醒. 为0时候下一节点还在acquire循环中,至少还有一次循环会去判断是否是头结点并执行tryAcquire,此时下一节点并没有park,所以这里不需要unpark.

AQS应用

ReentrantLock

final void lock() {
    if (!initialTryLock())
        acquire(1);
}

//公平锁的加锁逻辑
final boolean initialTryLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //判断CLH队列中是否还有其他线程在等待,没有的话尝试将state由0改为1
        //公平锁和非公平锁唯一区别就是是否需要判断hasQueuedThreads,非公平锁上来就是尝试
        //cas将state由0改为1,不去判断队列中是否已经有线程在等待.
        if (!hasQueuedThreads() && compareAndSetState(0, 1)) {
            //AQS中设置exclusiveOwnerThread变量为当前线程
            setExclusiveOwnerThread(current);
            return true;
        }
    //如果AQS中exclusiveOwnerThread变量代表的线程和当前线程一致,直接修改state变量即可,
    //reentrantLock本身就是可重入锁,支持这样多次加锁
    } else if (getExclusiveOwnerThread() == current) {
        if (++c < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(c);
        return true;
    }
    return false;
}

ReentrantReadWriteLock

先看下在读写锁中state是如何使用的,毕竟要保存读锁和写锁状态
AQS中state是int类型32位的,读写锁中使用高16位保存读锁状态,低16位保存写锁状态

        static final int SHARED_SHIFT   = 16;
        static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
        static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
        static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

        /** Returns the number of shared holds represented in count. */
        static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
        /** Returns the number of exclusive holds represented in count. */
        static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

下面是读锁的逻辑,AQS acquire方法已经实现了整体逻辑了,我们只需要按需实现tryAcquire或者tryAcquireShared方法即可, 这里读锁多个线程都可进入,所以实现的是tryAcquireShared

//加了读锁的线程都会有,记录当前线程加了几次读锁,避免加了两次锁,释放了三次情况
//holderCounter保存在threadLocal中
static final class HoldCounter {
    int count;          // initially 0
    // Use id, not reference, to avoid garbage retention
    final long tid = LockSupport.getThreadId(Thread.currentThread());
}

protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. If write lock held by another thread, fail.
     * 2. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 3. If step 2 fails either because thread
     *    apparently not eligible or CAS fails or count
     *    saturated, chain to version with full retry loop.
     */

    // AQS的state是int类型,在读写锁中将state高16位
    Thread current = Thread.currentThread();
    int c = getState();
    //未加写锁,或者加了写锁但是写锁是当前线程加的
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    // cas成功给state高16位读锁+1代表加读锁成功,成功后给自己线程的holdCounter+1
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null ||
                rh.tid != LockSupport.getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}

接下来看下写锁tryAcquire方法

protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        //c不为空表示一定是加了读锁或者写锁的,写锁=0,表示加了读锁,或者写锁!=0,但是加锁的线程
        //不是当前线程,这种情况加锁失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire 
        //这里当前线程已经获取写锁了,这里是重入,不需要cas去设置state
        setState(c + acquires);
        return true;
    }
	//state=0表示没有加读锁或者写锁,cas尝试加锁
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

CountDownLatch

经常忘记countDownLatch的用法, 它可以让一个线程等待,直至所有其他线程完成各自工作. 它提供了两个方法,countDown和await,工作线程完成后调用countDown,总的线程调用await去等待工作线程完成.

public void countDown() {
    sync.releaseShared(1);
}
//countDown就是做一个释放锁的操作
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c - 1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

//await是获取共享锁的操作,意味着可以有多个线程都在await,当countDown将state减到0时,就会唤醒
//所有await的线程
public void await() throws InterruptedException { 
    sync.acquireSharedInterruptibly(1);
}
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

CycliBarrier

CycliBarrier用法也总是想不起来,还是不太常用加上不熟悉原理缘故. CyCliBarrier可以翻译为循环栅栏,即当所有线程都到达栅栏时候,同时再往后执行.CyCliBarrier可以指定一个数字N,N个线程执行await等待其他线程到达,当最后一个线程执行await时,所有线程取消park,继续往后执行.
这里就不贴代码了,讲下原理.
CyCliBarrier使用了reentrantlock以及一个condition来实现,有一个变量count,当线程执行await时候,lock加锁,count-1并判断是否为0,即是否是最后到达线程,非最后一个的话,执行condition.await,最后一个线程的话执行condition.signalAll,

AQS条件队列

总结下条件队列,node加入到条件队列不用考虑多线程竞争的情况,await调用enableWait再调用isHeldExclusively,这个方法需要继承AQS的去实现,目前只有reentrantlock,reentrantReadWriteLock实现了,都是去判断AQS exclusiveOwnerThread变量是否为当前线程,这样相当于只有一个变量可以执行await和signal操作,就不需要考虑多线程竞争情况.
condition的使用需要在lock范围内,即condition的await与signal操作需要再lock.lock以及lock.unlock范围内.
每次new Condition其实是new了一个ConditionObject,ConditionObject包含firstWaiter,lastWaiter,所以ConditionObject其实就是个队列,线程可以park在不同的ConditionObject上
执行condition.signal不会实际去唤醒下一个node节点,只是把下一个node节点加入到CLH队列,因为执行condition.signal需要在锁里面,当前线程最后会执行lock.unlock这种操作,实际上会去执行aqs release操作,里面会去执行aqs signalNext,才会去实际唤醒下一个节点.

private void doSignal(ConditionNode first, boolean all) {
    while (first != null) {
        ConditionNode next = first.nextWaiter;
        //下一个节点变成firstWaiter
        if ((firstWaiter = next) == null)
            lastWaiter = null;
        //first节点状态包含cond
        if ((first.getAndUnsetStatus(COND) & COND) != 0) {
            //加入到CLH队列
            enqueue(first);
            if (!all)
                break;
        }
        first = next;
    }
}

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    ConditionNode node = newConditionNode();
    if (node == null)
        return;
    //当前线程执行release释放状态,唤醒后继节点,设置node状态为cond|wait
    int savedState = enableWait(node);
    LockSupport.setCurrentBlocker(this); // for back-compatibility
    boolean interrupted = false, cancelled = false, rejected = false;
    //检查node是否在CLH队列中,不在队列中进入下面循环
    while (!canReacquire(node)) {
        //等价于if(interrupted=interrupted|hread.interrupted())
        //如果线程中断过就进入这个if
        if (interrupted |= Thread.interrupted()) {
            //第一次进这个if时,node状态为cond|wait,get时候返回为3,3&2!=0
            //即中断后会退出while循环
            if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0)
                break;              // else interrupted after signal
        }
        //判断node状态是否包含cond状态,包含的话执行park操作
        else if ((node.status & COND) != 0) {
            try {
                if (rejected)
                    node.block();
                else
                    ForkJoinPool.managedBlock(node);
            } catch (RejectedExecutionException ex) {
                rejected = true;
            } catch (InterruptedException ie) {
                interrupted = true;
            }
        } else
            Thread.onSpinWait();    // awoke while enqueuing
    }
	//退出循环有两种情况,一种是中断退出,一种是其他线程执行了condition.signal将当前node加入到CLH队列,
    //node此时还是park的,直到被CLH队列头结点执行release时候方法内的signalNext唤醒,才会继续执行下面代码
    LockSupport.setCurrentBlocker(null);
    node.clearStatus();
	//不管是中断执行到这的还是正常被condition.signal后才到这的,都需要重新获取锁
    acquire(node, savedState, false, false, false, 0L);
    if (interrupted) {
        if (cancelled) {
            unlinkCancelledWaiters(node);
            throw new InterruptedException();
        }
        Thread.currentThread().interrupt();
    }
}

BlockingQueue

ArrayBlockingQueue

ArrayBlockQueue和我们自己实现的生产者消费者差不多,底层都是一个数组,一个reentrantlock,根据reentranlock生成的emptyCondition,fullCondition.
放入数据的时候如果满了,就调用fullCondition.await,让放数据线程进入等待,否则放入数据并调用emptyCondition.signal.
消费数据和放数据同理,数组为空时候,调用emptyCondition.await,否则正常消费数据并调用fullCondition,signal

LinkedBlockingQueue

之前以为linkedBlockingQueue的实现会和arrayBlockingQueue一样,只是一个底层是数据,一个是链表而已,结果并不是. 下面看下LinkedBlockingQueue的实现.
LinkedBlockingQueue使用了两个reentrantLock,taekLock和putLock,takeLock有一个notEmptyCondition,putLock有一个notFullCondition. 同时还有一个AtomicInteger类型的count,记录当前queue里面数量.
放入逻辑如下
放入数据的时候使用putLock加锁,满了就调用notFullCondition.await,否则正常放入.
如果放入后还有容量的话,调用notFullCondition.signal.这里和arrayBlockQueue不一样,array是由take操作调用notFullCondition.signal,这里是由put操作调用.
放入前如果count=0,调用notEmpty.signal,count=0表明可能有take操作被阻塞了需要被唤醒.
take数据逻辑就不描述了,和放入是一样的.

Array与Linked原理不同点

Array的实现put,take用的都是同一把锁,Linked用了两把锁,put,take不会互相阻塞,理论上Array也是可以用两把锁来实现的.自己实现一个. 见com.freedom.thread.aqs.MyArrayBlockingQueue

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值