压轴大戏:J.U.C之AQS

J.U.CAQS

一、AQS简介

AQS的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。子类必须定义更改此状态的受保护方法,并定义哪种状态对于此对象意味着被获取或被释放。假定这些条件之后,此类中的其他方法就可以实现所有排队和阻塞机制。子类可以维护其他状态字段,但只是为了获得同步而只追踪使用 getState()setState(int) 和 compareAndSetState(int, int) 方法来操作以原子方式更新的 int 值。 

AQS会把所有请求线程构成一个CLH队列,当一个线程执行完后(如解锁后lock.unlock)会激活自己的后继节点,但正在执行的线程并不在队列中,而那些等待执行的线程全部处于阻塞状态,经过调查线程的显式阻塞是通过调用LockSupport.park完成,而LockSupport.park则调用sun.misc.Unsafe.park本地方法,再进一步,HotSpotLinux中通过调用pthread_mutex_lock函数把线程交给系统内核进行阻塞。

该队列如图:

synchronized相同的是,这也是一个虚拟队列,不存在队列实例,仅存在节点之间的前后关系,通过headtailnext来关联。当有线程竞争琐时,该线程会首先尝试获得锁,这对于那些已经在队列中的线程来说就不公平,这也是非公平锁的由来,但是这样会提高吞吐量。如果已存在Running线程,则新的竞争线程会被追加到队尾,采用基于CASLock-Free算法。

二、ReentrantLock的调用过程

时序图:

三、源码分析

加锁过程:

1. Lock.lcok:

    public void lock() {

        sync.lock();

}

final static class NonfairSync extends Sync { //非公平锁的lock方法

        final void lock() {

            if (compareAndSetState(0, 1)) //A

                setExclusiveOwnerThread(Thread.currentThread()); //B

            else

                acquire(1);//C

        }

}

A:  CAS设置state10表示没有任何线程持有锁

B: 如果CAS成功,表示拿到了锁,则设定当前线程为锁的所有者

C: 如果CAS失败,表示获取锁失败,则再次请求锁,如果失败则加入等待队列,并设置为阻塞状态,知道被中断或有线程释放锁时被唤醒。

2. NonfairSync .Acquire实际上是AQS的模板方法

    public final void acquire(int arg) {

        if (!tryAcquire(arg) &&

            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

            selfInterrupt();

    }

2.1 tryAcquire : NonfairSync .tryAcquire: 实际调用子类的实现

        protected final boolean tryAcquire(int acquires) {

            return nonfairTryAcquire(acquires);

        }

        final boolean nonfairTryAcquire(int acquires) {

            final Thread current = Thread.currentThread();

            int c = getState();

            if (c == 0) { //A

                if (compareAndSetState(0, acquires)) {

                    setExclusiveOwnerThread(current);

                    return true;

                }

            }

            else if (current == getExclusiveOwnerThread()) {//B

                int nextc = c + acquires;

                if (nextc < 0) // overflow

                    throw new Error("Maximum lock count exceeded");

                setState(nextc);

                return true;

            }

            return false; //C

        }

A: 如果state=0,表示锁空闲,则CAS尝试获取锁,如果成功,设置锁的所有者为自己,返回true

B:如果state!=0,表示锁已经被别的线程拿到了,判断此时锁的持有者是不是自身,如果是,说明可以重入,这就是ReentrantLock的可重入特性,因为此时没有竞争,直接令state加一,返回true。这也就是每次ReentrantLock重入都会加一的核心地方,之后的每次unlock就会-1.

C. 如果state!=0,且锁不是自己拿到的,则返回false,说明再次尝试获取锁失败。获取失败后!tryAcquire(arg)方法返回true,就进入了后续的“与”代码。

2.2 addWaiterAQS的模板方法

    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) { //A如果队尾已存在

            node.prev = pred; //B把新节点挂在队尾节点后边

            if (compareAndSetTail(pred, node)) { //CAS 设置tail节点为新节点,防止两个线程同时执行了B,这种情况tail就挂了两个后续节点

                pred.next = node;

                return node;

            }

        }

        enq(node); //如果tail为空,或者CAS失败,则直接入队,继续CAS

        return node;

    }

    private Node enq(final Node node) {

        for (;;) {

            Node t = tail;

            if (t == null) { // Must initialize,如果tail为空

                Node h = new Node(); // Dummy header,构造哑节点

                h.next = node;

                node.prev = h;

                if (compareAndSetHead(h)) { //设置head节点

                    tail = node;

                    return h;

                }

            }

            else { //如果tail非空

                node.prev = t; //新节点的前指针指向tail

                if (compareAndSetTail(t, node)) { //tail指向node节点

                    t.next = node; //tnext指向新节点

                    return t;

                }

            }

        }

    }

通过enq保证如果有多个线程addWaiter加入队尾时CAS失败能继续加入。

成功加入CLH队列后,就继续执行acquireQueued方法,让其阻塞

2.3 acquireQueuedAQS的模板方法,设置新入队的线程阻塞

    final boolean acquireQueued(final Node node, int arg) {

        try {

            boolean interrupted = false;

            for (;;) {

                final Node p = node.predecessor(); // 取当前线程的前置节点

                if (p == head && tryAcquire(arg)) { //如果是头节点,尝试获取锁

                    setHead(node); //获取锁成功,则当前节点设为头结点

                    p.next = null; // help GC

                    return interrupted;

                }

                if (shouldParkAfterFailedAcquire(p, node) &&

                    parkAndCheckInterrupt()) //阻塞线程的精髓

                    interrupted = true;

            }

        } catch (RuntimeException ex) {

            cancelAcquire(node);

            throw ex;

        }

}

    private void setHead(Node node) {

        head = node;

        node.thread = null;

        node.prev = null;

    }

2.3.1shouldParkAfterFailedAcquire

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {

        int s = pred.waitStatus;

        if (s < 0) //A

            /*

             * This node has already set status asking a release

             * to signal it, so it can safely park

             */

            return true;

        if (s > 0) {//B

            /*

             * Predecessor was cancelled. Skip over predecessors and

             * indicate retry.

             */

    do {

node.prev = pred = pred.prev;

    } while (pred.waitStatus > 0);

    pred.next = node;

}

        Else //C

            /*

             * 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, 0, Node.SIGNAL);

        return false;

}

Node的状态:

        static final int CANCELLED =  1;

        /** waitStatus value to indicate successor's thread needs unparking */

        static final int SIGNAL    = -1;

        /** waitStatus value to indicate thread is waiting on condition */

        static final int CONDITION = -2;

A: 如果前继节点状态为signal,表明当前节点需要unpark,返回成功,则parkAndCheckInterrupt方法会让线程阻塞

B:如果前继节点状态为 CANCELLED(ws>0),说明前置节点已经被废弃,则回溯到一个非取消的前继节点,返回false,然后继续轮询,直到A返回ture,线程阻塞。

C:如果为其他状态,则设置前继节点状态位Signal,返回false,继续轮询。这里重试了两次,第一次标记为signal,准备阻塞,但是还不做阻塞的操作,重试一次如果不成功获得锁则真正阻塞。

此方法主要是根据前继节点来判断当前线程是否应该被阻塞的,如果前继节点处于CANCELLED状态,则删除这些节点重新构造新队列。

解锁过程:

AQS.release:

    public final boolean release(int arg) {

        if (tryRelease(arg)) { //尝试释放锁

            Node h = head; //获取头节点

            if (h != null && h.waitStatus != 0)

                unparkSuccessor(h); //唤醒头节点

            return true;

        }

        return false;

    }

ReentrantLock.sync.tryRealease:

        protected final boolean tryRelease(int releases) {

            int c = getState() - releases;

            if (Thread.currentThread() != getExclusiveOwnerThread())

                throw new IllegalMonitorStateException();

            boolean free = false;

            if (c == 0) { //只有state=0时,才返回true,才说明完全释放了锁

                free = true;

                setExclusiveOwnerThread(null); 

            }

            setState(c);

            return free;

        }

AQS.unparkSuccessor:

    private void unparkSuccessor(Node node) {

        /*

         * Try to clear status in anticipation of signalling.  It is

         * OK if this fails or if status is changed by waiting thread.

         */

        compareAndSetWaitStatus(node, Node.SIGNAL, 0);

        /*

         * Thread to unpark is held in successor, which is normally

         * just the next node.  But if cancelled or apparently null,

         * traverse backwards from tail to find the actual

         * non-cancelled successor.

如果当前节点被取消或指向空,从tail节点开始遍历,找到非取消状态的节点

         */

        Node s = node.next;

        if (s == null || s.waitStatus > 0) {

            s = null;

            for (Node t = tail; t != null && t != node; t = t.prev)

                if (t.waitStatus <= 0)

                    s = t;

        }

        if (s != null)

            LockSupport.unpark(s.thread);

    }

四、总结

在基于AQS构建的同步器类中,最基本的操作包括各种形式的获取操作和释放操作。获取操作是一种依赖状态的操作,并且通常会阻塞。当使用锁或信号量时,“获取”操作的含义就很直观,即获取的是锁或者许可,并且调用者可能会一直等待直到同步器类处于可被直接获取的状态。在使用CountDownLatch时,“获取”操作意味着“等待并直到闭锁达到结束状态”,而使用FutureTask时,则意味着“等待并直到任务已经完成”。“释放”并不是一个可阻塞的操作,当执行“释放”操作时,所有在请求时被阻塞的线程都会开始执行。

如果一个类想成为状态依赖的类,那么它必须拥有一些状态。AQS负责管理同步器类中的状态,它管理了一个整数状态信息,可以通过getStatesetState以及compareAndSetStateprotected类型方法来进行操作。这个整数可以用于表示任意状态。例如,ReentrantLock用它来表示所有者线程已经重复获取该锁的次数,Semaphore用它来表示剩余的许可数量,FutureTask用它来表示任务的状态。

AQS中获取操作和释放操作的标准形式:

Boolean acquire() throws InterruptedException{

While(当前状态不允许获取操作){

    if(需要阻塞获取请求){

          如果当前线程不在队列中,则将其插入队列

          阻塞当前线程    

    } else {

      返回失败

    }

}

可能更新同步器的状态

如果线程位于队列中,则将其移出队列

返回成功

}

Void release() {

更新同步器的状态

If(新的状态允许某个被阻塞的线程获取成功)

    解除队列中一个或多个线程的阻塞状态

}

根据同步器的不同,获取操作可以是一种独占操作(如ReentrantLock),也可以是一个非独占操作(如SemaphoreCountDownLatch)。如果支持独占,需要实现一些保护方法,包括tryAcquiretryReleaseisHeldExclusively等,而对于支持共享的同步器,则应该实现tryAcquireSharedtryReleaseShared等。

为了将此类用作同步器的基础,需要适当地重新定义以下方法,这是通过使用 getState()setState(int) 和/或 compareAndSetState(int, int) 方法来检查和/或修改同步状态来实现的:

· tryAcquire(int)

· tryRelease(int)

· tryAcquireShared(int)

· tryReleaseShared(int)

· isHeldExclusively()

默认情况下,每个方法都抛出 

参考:http://suo.iteye.com/blog/1329460 《Java并发编程实战》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值