乱弹java并发(五)-- AQS

AQS(AbstractQueuedSynchronizer)是JDK中大部分同步工具:可重入锁(ReentrantLock)、可重入读写锁(ReentrantReadWriteLock)、计时闩(CountDownLatch)、信号量(Semaphore)、异步任务(FutureTask)实现的基础。它提供了五个模板方法:

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }

     protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }

    protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }
上述几个方法分别在获取独占锁、释放独占锁、获取共享锁、释放共享锁、判断同步器是否被当前线程独占持时被调用,同步工具通过复写这几方法来实现特有的同步逻辑。

AQS的内在核心实现是由CAS实现的一个非阻塞的双向链表队列,每个结点对应一个参与线程,队列的头结点(head)的后续结点表示持有锁的线程,尾结点表示最后一个排队线程,还有一个关键属性state,各个同步工具根据这个字段来扩展自身的同步语义,比如ReentrantLock用state来表示锁被某个线程重入的次数,CountDownLatch用state来表示剩余的计数,Semaphore用它来表示剩余的信号量,FutureTask用它来表示当前的任务状态等。每个结点有三种状态(JDK6不同的版本实现有点不一样,有的小版本多了一个传播态PROPAGATE,这个状态是来优化性能的,跟实现逻辑无关):

CANCELLED:表示节点所对应的线程已被取消,比如等待超时或者线程被中断。该状态下的节点在后续的入队时会被删除出队列

SIGNAL:表示当前节点的后继结点被挂起,当当前节点释放同步或被取消时由对应的线程唤醒后续节点对应的线程。

CONDITION:当前结点对应的线程处于条件等待队列,比如线程调用了Condition.await方法之后。

下面以ReentrantLock为例子来分析AQS的代码流程,ReentrantLock锁有两种模式:非公平和公平模式,这两种模式的区别是非公平模式下,线程在入队之前如果发现当前锁没有被占用会先尝试抢占锁,如果抢占成功就在之前的排队线程之前持有锁,抢占失败入队尾等待,而公平模式下线程直接入队等待,非公平模式能够降低线程被挂起的概率,减少上下文切换的次数,这种模式下的CPU吞吐率比公平模式要高,但是有可能产生饥饿,有些线程可能会长时间等待获取不到锁。下面主要从公平模式分析,公平模式的AQS实现是ReentrantLock的内部类FairSync。

  final static class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (isFirst(current) &&
                    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;
        }
    }
线程获取锁时会调到FairSync的lock方法,lock方法直接调到AQS的acquire

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
然后在进入模板方法tryAcquire尝试获取锁,FairSync的tryAcquire方法如下:

    protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (isFirst(current) &&
                    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;
        }
在tryAcquire中,先判断state字段是否为0,如果为0说明当前锁没有被任何线程占有,判断当前线程是否是等待队列中的第一个线程(队列为空或者head结点的后继结点对应的线程就是当前线程),如果当前线程是第一个线程调用CAS操作设置state,调用成功之后设置锁的持有线程为当前线程,返回true说明成功获得了锁,如果CAS设置state调用失败,说明该锁已经被其它线程抢先占有则返回false表明获取锁失败。如果state字段不为0说明当前锁已经被线程占有,判断占有线程是不是自己,如果是state字段加1,如果不是返回获取锁失败。

回到acquire方法,当tryAcquire放回true说明获取锁成功,该方法调用结束,线程被放行继续后面的代码调用,如果返回false,为当前线程创建一个独占模式的结点并且入队,入队之后检查当前节点的前继结点是否是head节点,如果是的话再次调用tryAcquire尝试获取锁如果成功线程被放行继续后面的代码调用,如果失败说明锁被抢占继续循环检查前继结点是否为头结点,如果前继结点不是头结点,挂起线程(如果还没有取消),把前继结点的状态设置成SIGNAL,在这个过程同时也会删除已被取消的节点,线程挂起之后会在parkAndCheckInterrupt方法中阻塞。之所以这样反复检查反复调用tryAcquire,是因为AQS是基于非阻塞算法实现的,而在并发条件下,队列的状态随时都有可能发生变化,所以要随时检查状态以免出现状态不一致。

    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;
    }

    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 static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int s = pred.waitStatus;
        if (s < 0)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park
             */
            return true;
        if (s > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
<span style="white-space:pre">	</span>    do {
<span style="white-space:pre">		</span>node.prev = pred = pred.prev;
<span style="white-space:pre">	</span>    } while (pred.waitStatus > 0);
<span style="white-space:pre">	</span>    pred.next = node;
<span style="white-space:pre">	</span>}
        else
            /*
             * 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;
    }
上面ReentrantLock获取锁的过程就分析完了,下面来看看释放锁的过程:

    public void unlock() {
        sync.release(1);
    }

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    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.
         */
        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);
    }
    protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
在释放锁时首先调用tryRelease尝试释放锁,首先把state状态减掉1,检查当是不是被当前线程持有,如果不是说明程序中错误地调用了unlock方法(线程没有调lock方法就调用了unlock方法)抛出异常,然后state是否是0,如果不是0说明锁被当前线程重入了超过一次,更新state字段释放锁失败,如果是0则释放返回true说明可以释放成功,把当前锁的持有线程设成null。回到release方法,tryRelease如果返回false方法结束,锁没有没有被释放,如果返回true,唤醒后继结点对应的线程,在唤醒的过程中会过滤掉已被取消的结点,唤醒第一个非取消态的结点。线程被唤醒之后从parkAndCheckInterrupt中继续执行,如果线程没有被中断则在acquireQueued中继续循环检查。以上就是AQS的核心原理




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值