AbstractQueuedSynchronizer那些事儿(二) 数据结构

在继续深入底层细节前我们应该先了解AQS的成员变量有哪些,管中窥豹,先理解它的数据结构才能更好的搞清楚细节实现。
申明一下,很多理解都是直接写在代码注释中的,我认为采用这种方式可以更好的与代码亲密接触。

成员变量

    //等待队列的头结点,惰性初始化,除了初始化,只能通过setHead方法修改。如果头结点不为null,则它的状态一定不会是CANCELLED
    private transient volatile Node head;

    //等待队列的尾节点,惰性初始化,只能通过enq方法添加。
    private transient volatile Node tail;

    //同步状态,初始化时该值为0
    private volatile int state;
    //更快自旋而不是限时的阻塞
    static final long spinForTimeoutThreshold = 1000L;

可以看到所谓的等待队列其实是一个双向链表,每个入队线程都会被包装成一个Node节点塞入队列中,而且这些变量都使用了volatile来保证内存可见性。
AbstractQueuedSynchronizer那些事儿(二) 数据结构_第1张图片
state状态对于AQS来说至关重要,所以为了保证线程安全,AQS通过unsafe方法来原子更新该值,注意下面的方法都采用了protected final修饰来保证子类可以访问但不能修改,在AQS中大量的方法实现都采用了final关键字以防止子类重写。

    protected final int getState() {
        return state;
    }

    protected final void setState(int newState) {
        state = newState;
    }
    
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

静态内部类Node

Node对于AQS来说至关重要,因为他是等待队列维护的基本单元,Doug Lea的注释非常详细的描述了它的数据结构和工作机制。

AQS实现了CLH锁队列的一个变种,CLH锁一般用作自旋锁。每个节点中的state域描述了一个线程是否应该阻塞。如果一个节点的前驱节点释放了,那么该节点会收到通知。state域并不保证线程一定会获得锁,队列中的第一个节点可以尝试去获取,但是不保证获取成功,只是给了它竞争的机会,因此它可能需要重新等待。
入队只需要添加一个tail节点,出队只需要设置head节点。
入队只需要保证tail节点的原子操作,类似的出队调用仅仅更新head节点。然而实际上,我们可能需要一点额外的工作来查询后继节点是谁,因为节点有可能因为超时或者中断被取消。
prev指针(在CLH中没有用到)主要用来处理可取消节点,当然在CLH中没有用到。如果一个节点被取消,它的后继应该指向它的未取消前驱节点。
next指针用来实现一个阻塞语义。每个node维护了一个线程,所以一个节点的前驱节点传递next指针来决定下个需要被唤醒的线程。因为新入队的节点会修改前驱节点的next指针,所以后继节点的决定应该要避免竞争
CLH需要启动一个虚的head节点,但是我们不在构造器初始化,因为如果没有竞争,这将会导致无用功。相反,在第一次竞争时会创造这个节点和head,tail指针。
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;

        /*
        SIGNAL:当前节点的后继节点很快或者已经被阻塞,所以当前节点在取消或者释放的时候需要唤醒后继节点。为了避免竞争,获取锁的方法必须首先表明他们需要一个信号并尝试原子获取锁,获取失败就阻塞。
        CANCELLED:当前节点因为中断或者超时被取消了,一旦变成这个状态就不会在改变,也不会再次阻塞。
        CONDITION:当前节点处于条件队列中,一般情况下不会进入到同步队列中,而一旦发生了转移,它的state会重置为0
        PROPAGATE:releaseShare方法的调用应该传播到其他节点,在doReleaseShared来设置该状态以确保传播继续。
        */
        volatile int waitStatus;
        //当前节点依赖于它的前驱节点检查waitStatus状态,入队的时候初始化,出队的时候赋null。如果前驱节点被取消,就找下一个未取消的前驱节点,总是能找到的,因为head节点不可能被取消。一个节点获取了许可就会成为head节点。
        volatile Node prev;
        //当前节点释放许可的时候需要唤醒的节点,入队操作并不直接分配前驱节点的next指针直到被附加上,所以看到next指针为null并不意味着那个节点已经是tail节点了。已经取消的节点的next指针指向它自己而不是null,为了isOnSyncQueue早起?这里翻译不好,后面代码再说
        volatile Node next;

        volatile Thread thread;

        //指向条件队列的节点或者共享模式的节点,因为条件队列只能在独占模式下访问。
        Node nextWaiter;
        //addWaiter调用
        Node(Thread thread, Node mode) {    
            this.nextWaiter = mode;
            this.thread = thread;
        }
        //Condition调用
        Node(Thread thread, int waitStatus) { 
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

你可能感兴趣的:(java,并发)