Java多线程之AbstractQueuedSynchronizer

简单介绍
AbstractQueuedSynchronizer这个词拆分解释就是抽象队列同步器的意思,一般简称AQS。Java许多并发工具类的内部实现都依赖于它,最常见的比如:ReentrantLock、CountDownLatch、Semaphore。

AQS的主要使用方式是继承它作为一个内部辅助类实现同步原语,像上面提到的几个并发工具类,其都包含了一个内部类Sync继承AQS来实现具体获取锁和释放锁的逻辑。它可以简化你的并发工具的内部实现,屏蔽同步状态管理、线程的排队、等待与唤醒等底层操作。

实现思路:
AQS内部维护一个CLH队列来管理锁。
线程会首先尝试获取锁,如果失败,则将当前线程以及等待状态等信息包成一个Node节点加到同步队列里。
接着会不断循环尝试获取锁(条件是当前节点为head的直接后继才会尝试),如果失败则会阻塞自己,直至被唤醒;
而当持有锁的线程释放锁时,会唤醒队列中的后继线程。

AQS结构:
可以看下AbstractQueuedSynchronizer的主要属性、内部类以及原子类方法:

// 链表头结点
private transient volatile Node head;

// 链表尾节点
private transient volatile Node tail;

// 线程获取锁的状态,默认为0代表还没有线程获取到锁,1代表有一个线程获取到了一次锁,大于1代表线程获取到了多次锁(重入锁的实现)
private volatile int state;

static final class Node {
	static final Node SHARED = new Node();
    static final Node EXCLUSIVE = null;
    
	// 表示当前的线程被取消
    // 当前线程所在节点的前继节点内线程状态为此值时,当前节点内线程可值机尝试获取锁而不用阻塞
    static final int CANCELLED =  1;
    
    // 表示当前节点的后继节点包含的线程需要运行,也就是unpark
    static final int SIGNAL    = -1;
    
    // 表示当前节点在等待condition,也就是在condition队列中
    static final int CONDITION = -2;
    
    // 表示当前场景下后续的acquireShared能够得以执行
    static final int PROPAGATE = -3;
    
    // 默认值为0,表示当前节点在sync队列中,等待着获取锁
    volatile int waitStatus;
    
    // 前驱节点,比如当前节点被取消,那就需要前驱节点和后继节点来完成连接
	volatile Node prev;
	
	// 后继节点
	volatile Node next
	
	// 存储condition队列中的后继节点
	volatile Thread thread;
	
	// 入队列时的当前线程
	Node nextWaiter;
}
    // 原子性的更新state
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
     // 原子性的更新头结点
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }
    // 原子性的更新尾节点
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }
    // // 原子性的更新节点等待状态
    private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);
    }
    // // 原子性的更新指定节点的下一个节点
    private static final boolean compareAndSetNext(Node node,Node expect,Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }

获取锁的实现逻辑:

// 获取锁的起始方法,在ReentrantLock中由lock()方法调用
public final void acquire(int arg) {
	// 先尝试获取锁,是否失败?
    if (!tryAcquire(arg) &&
    	// addWaiter(Node.EXCLUSIVE):将当前线程构造成一个队列放入道CLH队列尾部
    	// 在CLH队列中会检测当前线程构造的节点是否为head的直接后继,并尝试获取锁
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 中断当前线程
        selfInterrupt();
}

// 尝试获取锁状态,这是实际获取锁的实现逻辑。
// 这个由具体子类实现获取锁状态的逻辑,比如在ReentrantLock中有FairSync(公平)和NonfairSync(非公平,默认)的不同实现。
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

// 在CLH队列中会检测当前线程构造的节点是否为head的直接后继,并尝试获取锁
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        		// 当前线程构建的节点是否有前置节点,没有则抛出NullPointerException
                final Node p = node.predecessor();
                // 是否是头结点并且获取锁状态成功
                if (p == head && tryAcquire(arg)) {
                	// 将当前线程构建的节点设置为头结点
                    setHead(node);
                    // 将原本头结点的后继节点引用消除,用来回收原头结点
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 在这里需要根据当前线程构建节点的前继节点waitStatus状态来判断是否要阻塞。
                // 1、前驱节点为SIGNAL状态,在释放锁的时候会唤醒后继节点,所以后继节点(也就是当前节点)现在可以阻塞自己。
			 	// 2、前驱节点为CANCEL,向前遍历,更新当前节点的前驱为往前第一个非取消节点,当前节点会会再次回到循环并尝试获取锁。	
				// 3、等待状态为0或者PROPAGATE,设置前驱的等待状态为SIGNAL,并且之后会回到循环再次重试获取锁。
                // 如果未成功获取锁则根据前驱节点判断是否要阻塞
                // 如果阻塞过程中被中断,则置interrupted标志位为true
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
	
	// 将当前线程构造成一个队列放入道CLH队列尾部
    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;
    }

	// 初始情况或者在快速尝试失败后插入节点
    private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

释放锁的实现逻辑:

// 释放锁的起始方法,在ReentrantLock中由unlock()调用
public final boolean release(int arg) {
	// 释放锁,释放成功?
    if (tryRelease(arg)) {
    	// 若释放锁成功,先获取头结点的引用
        Node h = head;
        // 如果头结点不为空切头结点的waitStatus不等于0
        if (h != null && h.waitStatus != 0)
        	// 唤醒后继结点包含的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

// 是否锁的逻辑,实际有子类去做不同的实现
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

// 唤醒后继节点包含的线程
private void unparkSuccessor(Node node) {
	// 获取节点的等待状态
    int ws = node.waitStatus;
    // 是否小于0
    if (ws < 0)
    	// 将其更新为0
        compareAndSetWaitStatus(node, ws, 0);
	// 获取节点的后继节点
    Node s = node.next;
    // 若后继节点为null 或  后继节点的等待状态为CANCEL(大于0就只能是CANCEL)
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 从尾节点开始向前遍历
        for (Node t = tail; t != null && t != node; t = t.prev)
        	// 如果节点的等待状态不为CANCEL
            if (t.waitStatus <= 0)
            	// 将参数节点的后继节点设为此节点
                s = t;
    }
    // 若参数节点的后继节点为null
    if (s != null)
    	// 
        LockSupport.unpark(s.thread);
}

猜你喜欢

转载自blog.csdn.net/insomsia/article/details/88742999