JUC之AQS解读

JUC之AQS解读

一、队列同步器

​ AbstractQueueSynchronizer,即队列同步器,通常被称为AQS,是用于构造锁、同步组件的基础框架,它使用int表示同步状态,使用内置FIFO双向队列来完成线程的排队工作。

​ AQS可以说是整个juc锁框架的基础,可以这么说,AQS是面向实现锁,而同步锁是用于处理并发的场景。

二、设计思想

1、同步队列节点

​ 一个node节点的信息如下:

static final class Node {
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED = 1;
        static final int SIGNAL = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        volatile Thread thread;

        Node nextWaiter;

        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;
            THREAD.set(this, Thread.currentThread());
        }
        Node(int waitStatus) {
            WAITSTATUS.set(this, waitStatus);
            THREAD.set(this, Thread.currentThread());
        }

        final boolean compareAndSetWaitStatus(int expect, int update) {
            return WAITSTATUS.compareAndSet(this, expect, update);
        }

        final boolean compareAndSetNext(Node expect, Node update) {
            return NEXT.compareAndSet(this, expect, update);
        }

        final void setPrevRelaxed(Node p) {
            PREV.set(this, p);
        }

        // VarHandle mechanics
        private static final VarHandle NEXT;
        private static final VarHandle PREV;
        private static final VarHandle THREAD;
        private static final VarHandle WAITSTATUS;

        static {
            try {
                MethodHandles.Lookup l = MethodHandles.lookup();
              //对Node节点中的状态等信息变量的操作转化为CAS操作。
                NEXT = l.findVarHandle(Node.class, "next", Node.class);
                PREV = l.findVarHandle(Node.class, "prev", Node.class);
                THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
                WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
            } catch (ReflectiveOperationException e) {
                throw new Error(e);
            }
        }
    }

分析:Node节点中的一些状态的信息的改变,都是采用CAS操作。

2、同步状态处理(独占)

​ 当线程获取同步状态失败,就会被构造成一个Node加入队列,同时阻塞该线程,直到同步状态释放,就会唤醒线程,使其尝试获取同步状态。

​ 当一个线程获取同步状态失败,在加入同步队列的这个过程必须是线程安全的,因此,采用CAS加入队列。

	/**
     * CASes tail field.
     */
    private final boolean compareAndSetTail(Node expect, Node update) {
        return TAIL.compareAndSet(this, expect, update);
    }

​ 以下是获取独占锁的实现:

public final void acquire(int arg) {
    //如果获取失败,并且成功加入同步队列,则对该线程进行中断。
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
private Node addWaiter(Node mode) {
        Node node = new Node(mode);
  //通过死循环CAS尝试加入队尾。
        for (; ; ) {
            Node oldTail = tail;
            if (oldTail != null) {
                node.setPrevRelaxed(oldTail);
                if (compareAndSetTail(oldTail, node)) {
                    oldTail.next = node;
                    return node;
                }
            } else {
                initializeSyncQueue();
            }
        }
    }
  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
                  //成功获取同步状态,中断标志为false。
                    return interrupted;
                }
              //前驱节点不是头节点,则采用park挂起,并且进行中断。
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

​ 以上就是同步独占锁的获取机制:

  • 获取同步锁失败则进入队列,采用CAS尝试加入队列尾

  • 每个队列节点都通过自旋尝试获取同步状态

    • 判断前驱是否为头节点,是则进行同步状态的获取,并且线程的中断标识为false
    • 如果前驱不是头节点,则采用park进行挂起,并且设置中断标识为true

    以上是同步锁的获取过程,以下是释放同步状态的实现:

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) {
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);
        Node s = node.next;
        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;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

​ 可以看到,同步器的释放,是采用unpark唤醒后继节点。

3、同步状态处理(共享)

​ 除了独占锁,还支持共享锁,共享锁的意思是同一时刻能有多个线程获取同步状态。

​ 我们先看看共享锁是如何获取同步状态。

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
  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) {
                        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);
        }
    }

​ 可以发现,其实共享锁与独占锁的获取方式是相同的,只不过,共享锁允许多个线程同时持有同步状态,而独占锁则是同步状态只允许一个线程获取

question

​ 这里,可能有一个疑问,共享锁可以有多个线程获取,那么,与不加锁的区别在哪里,不就成了共享的么?

answer

  • 共享锁确实可以由多个线程获取,共享资源对于这些获取了共享锁的线程来说,就是共享的。
  • 这是DougLea大师的一个设计,确实,都是读资源共享可以不加锁,但是,通过加读锁,可以实现“读写分离”,也可以控制读并发的数量。
发布了57 篇原创文章 · 获赞 32 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/rekingman/article/details/98878091