AbstractQueuedSynchronizer源码阅读

AbstractQueuedSynchronizer 就是那个大名鼎鼎的 AQS,是java.util.concurrent包下同步器的核心。java

CLH(Craig, Landin, and Hagersten)锁

使用队列的方式来解决n个线程来争夺m把锁的问题,每当一个新的线程须要获取锁,为其建立一个节点并放到队尾,若是该线程是队列中的第一个节点,则节点的locked设置成false,若是它不是队列的第一个节点,则它的节点的prev指向原来的队尾节点,并不断自旋查看prev指向节点的locked属性,若是该值变为false,表示轮到它来尝试获取锁了,若是获取成功并最终用完释放后,则将本身的locked设置成false,若是获取失败,locked值不变,仍是true,并不断尝试获取锁。node

也就是说,每一个节点只须要关心前置节点的locked状态,能够发现CLH实现锁的获取是公平的。ui

Node节点

Node节点维护了双向节点和当前节点状态和线程引用。this

static final class Node {
volatile Node prev;
volatile Node next;
volatile Thread thread;

volatile int waitStatus; // SIGNAL CANCELLED CONDITION PROPAGATE

CANCELLED,值为1,表示当前的线程被取消;
SIGNAL,值为-1,表示当前节点的后继节点包含的线程须要运行,也就是unpark;
CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
PROPAGATE,值为-3,表示当前场景下后续的acquireShared可以得以执行;
值为0,表示当前节点在sync队列中,等待着获取锁。

}

FIFO队列

*      +------+  prev +-----+       +-----+
 * head |      | <---- |     | <---- |     |  tail
 *      +------+       +-----+       +-----+

核心属性

  • private transient volatile Node head; 等待队列首元素;
  • private transient volatile Node tail; 等待队列尾元素;
  • private volatile int state; 同步状态;

须要在锁定时,须要维护一个状态(int类型),而对状态的操做是原子和非阻塞的,经过同步器提供的对状态访问的方法对状态进行操纵,并基于Unsafe的原子操做来修改state的状态,compareAndSet来确保原子性的修改。线程

获取/设置当前的同步状态:code

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

入队操做,若是队列为空则实例化节点,不然插入队尾:get

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;
    }
  • protected boolean tryAcquire(int arg) 排它的获取这个状态。这个方法的实现须要查询当前状态是否容许获取,而后再进行获取(使用compareAndSetState来作)状态。
  • protected boolean tryRelease(int arg) 释放状态。
  • protected int tryAcquireShared(int arg) 共享的模式下获取状态。
  • protected boolean tryReleaseShared(int arg) 共享的模式下释放状态。
  • protected boolean isHeldExclusively() 在排它模式下,状态是否被占用。

获取锁: 须要获取当前节点的前驱节点,而且头结点可以获取状态,表明可以占有锁。同步

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

这个方法在非公平实现中,主要是经过AQS的state来检查和维护锁状态,若是state是0,说明没有线程占有这个锁,若是不为0而且锁的占有线程是当前线程,则是重入的状况,都可以得到锁并修改state值。it

若是是首次得到锁,则设置锁占有线程为当前线程。

固然,若是前面两种状况都不知足,说明尝试得到锁失败,须要作前面段落所述的队列操做,建立一个等待结点并进入循环,循环中的park()调用挂起当前线程。

不然将当前线程挂起:

LockSupport.park最终把线程交给系统(Linux)内核进行阻塞。

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

释放锁

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

若是修改state值成功,则找到队列中应该唤起的结点,对节点中的线程调用unpark()方法,恢复线程执行。

相关文章
相关标签/搜索