Java并发编程AQS及其实现类

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lovewebeye/article/details/79733223

并发之AQS(AbstractQueuedSynchronizer抽象的队列式的同步器,ReentrantLock/Semaphore/CountDownLatch等中的同步类实现都依赖于它。

AQS定义两种资源共享方式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)

  • tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  • tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  • tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  • tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
  • isHeldExclusively():该线程是否正在独占资源。

AQS实现好了同步器的框架,具体是独占式还是共享式,只需要根据情况实现上面的不同方法即可。

AQS核心是维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。

CountDownLatch/Semaphore在初始化的时候均需要设置资源数量state,也即有多少资源可以被共用,一般大于1个才能达到并发效果。

ReentrantLock,每次调用lock时会把资源state尝试设置成1,如果失败则调用tryAcquire(1)尝试将state+1,(其他线程调用tryAcquire会失败,如果还是自己这个线程调用才能成功使state+1, 这也就时可重入锁的意思),每次调用unlock会尝试调用tryRelease(1)释放资源,直到state=0为止。

————————————————————————————————

AQS方法解析

acquire(int)

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
1.尝试获取资源。
2.addWaiter将线程添加到等待队列尾部,标记为独占模式。
3. acquireQueued通过循环调用tryAcquire尝试获取资源,如果获取失败,则调用park方法使线程处于阻塞状态, 如果获取成功则返回。
4 . 获取到资源后,执行selfInterrupt() 调用Thread.currentThread().interrupt();

tryAcquire(int)

尝试获取资源,AQS中并没有实现. 需要子类实现,子类可以根据需要实现。

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

release(int)

 public final boolean release(int arg) {

    if (!tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus !=0)
             unparkSuccessor(h);
         return true;
    }
    return false;
}

其中waitStatus : 

    SIGNAL : 1 表示线程阻塞中,需要unpark; 

 CANCELLED:-1 线程被取消,表示由于超时或者interrupt,非阻塞状态。

 CONDITION :-2 表示当前节点在等待condition,也就是在condition队列中。

 PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行; 

 值为0,表示当前节点在sync队列中,等待着获取锁。

UnparkSuccessor(Node)方法:从尾部向前查找该节点之后并且最靠前的需要唤醒的资源,并唤醒(调用LockSupport.unpark)。

——————————————————————————————————————————————

acquireShared(int)

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

通过tryAcquireShared()尝试获取锁,如果获取失败则通过doAcquireShared()进入等待队列,直到获取到资源为止才返回。

doAcquireShared(int) :尝试获取共享资源,直到获取到资源。

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);//共享模式
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();//获取前一个节点
            if (p == head) {//如果是头节点 尝试获取资源
                int r = tryAcquireShared(arg);
                if (r >= 0) {//资源获取成功,r为剩余资源数量
                    setHeadAndPropagate(node, r);//如果还有剩余资源,可以继续唤醒其他线程
                    p.next = null; // help GC
                    if (interrupted)//如果中断过
                        selfInterrupt();//中断
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

shouldParkAfterFailedAcquire

检查前一个节点状态,是否为SIGNAL状态,如果是返回true,如果不是,则设置成SIGNAL状态。

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

setHeadAndPropagate(Node,int)

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    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(); //尝试释放剩余共享资源
    }
}

releaseShared(int) 释放共享资源

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

doReleaseShared(int) 释放共享资源

private void doReleaseShared() {

    for (;;) {
        Node h = head; 
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {//需要被unpark唤醒
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//设为释放状态,失败,继续循环。
                    continue;            // loop to recheck cases
                unparkSuccessor(h);// 通过调用 LockSupport.unpark,唤醒资源
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

——————————————————————————————————————————————————————

1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:

  CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;

  而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;

  另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。

展开阅读全文

没有更多推荐了,返回首页