谈谈AQS(一)- 同步组件的基石

AbstractQueuedSynchronizer-最基础的同步器模板

它是一个框架,一个模板

AQS只是一个框架,具体资源的获取/释放方式(tryAcquire/tryRealease)交由自定义同步器去实现,是一个抽象的模板类,体现了模板方法的设计模式。

核心变量state

/**
 * The synchronization state.
 */
private volatile int state;
复制代码

AQS甚至于concurrent包中的所有类,都是在对volatile变量读写结合CAS操作的基础上实现的。

可以重写的方法(没有限定final):

//独占式获取同步状态,需要CAS更新同步状态
protected boolean tryAcquire(int arg);

//独占式释放同步状态
protected boolean tryRealease(int arg);

//共享式获取同步状态,返回值大于0表示成功,反之失败
protected boolean tryAcquireShared(int arg);

//共享式释放同步状态
protected boolean tryRealeaseShared(int arg);

//当前同步器是否在独占模式下被线程占用
protected boolean isHeldExclusively();
复制代码
  • 这些方法都没有直接声明成abstract,而是直接抛出异常。也就意味着不是每个子类都必须实现这些方法,比如独占锁就完全不需要去实现和它不相关的shared方法。

不可重写的方法(直接声明final):

public final void acquire(int arg);
public final void acquireInterruptibly(int arg);
public final void acquireShared(int arg);
public final void acquireSharedInterruptibly(int arg);
public final boolean tryAcquireNanos(int arg, long nanos);
public final boolean tryAcquireSharedNanos(int arg, long nanos);
public final boolean release(int arg);
public final boolean releaseShared(int arg);
public final Collection<Thread> getQueueThreads();
复制代码

独占式的同步状态获取流程

首先调用acquire

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
复制代码
  • tryAcquire成功则结束,直接返回,可以开始执行临界区的代码
  • 否则,要将当前的线程构建成一个Node.EXCLUSIVE模式的节点,加入到队列的末尾,并且调用acquireQueued方法,让这个节点进入自旋状态。也就是说,这个节点加入同步队列后,就会死循环的去获取同步状态,从而阻塞在了临界区的外面。该线程的唤醒主要依靠前驱结点的出队或者阻塞线程被中断。
private Node addWaiter(Node mode) {
    /**	
     *	以一个Node类的静态变量Node.EXCLUSIVE/Node.SHARED
     *  作为mode标志节点的模式
     *	mode节点会被设置成nextWaiter
     *	所以nextWaiter可用来判断节点的两种模式
     */
    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;
}
复制代码
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);
    }
}
复制代码

独占式同步状态的释放

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 boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
复制代码

共享式同步状态的获取

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

虽然是共享式的获取同步状态,但也不意味着这个同步状态可以被无限的获取,还是有一个限制的。例如Semaphore中的permits即是一个限制。

final int nonfairTryAcquireShared(int acquires) {
	for (;;) {
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
     }
}
复制代码

当remaining < 0 时,就会触发tryAcquireShared(arg) < 0, 也就是尝试共享式的获取失败。 那么这时候也会像独占式时那样,加入同步队列,自旋的去获取同步状态。也就是超过state允许的最大值后,也不能去执行临界区的代码。 唯一的区别就是,将当前线程封装成了一个Node.SHARED模式的节点。在自旋状态中尝试获取成功后,会重新设置头节点并且将这个影响借助链表中的节点以及节点的waitStatus传播下去。

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);
    }
}
复制代码

LockSupport工具包

以上方法中关于对线程的阻塞和唤醒主要通过LockSupport这个包中的几个方法来实现。

  • void park() : 阻塞当前线程,线程不再参与处理器调度,如果调用unpark方法或者当前线程被中断,才能从park()中返回。
  • void parkNanos(long nanos): 增加超时返回的功能。
  • void parkUntil(long deadline):将线程阻塞到deadline时间。

Doug Lea 为了演示这个类的基本功能,不借助aqs实现了一个先进先出的互斥锁。

class FIFOMutex {
	private final AtomicBoolean locked = new AtomicBoolean(false);
	private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();

	public void lock() {
	  	boolean wasInterrupted = false;
	  	Thread current = Thread.currentThread();
	  	waiters.add(current);

	  	// Block while not first in queue or cannot acquire lock
	  	while (waiters.peek() != current ||
	         !locked.compareAndSet(false, true)) {
		    LockSupport.park(this);
		    if (Thread.interrupted()) // ignore interrupts while waiting
		      	wasInterrupted = true;
  		}

	  	waiters.remove();
	  	if (wasInterrupted)          // reassert interrupt status on exit
		    current.interrupt();
	}

 	public void unlock() {
	   	locked.set(false);
	   	LockSupport.unpark(waiters.peek());
 	}
}
复制代码

猜你喜欢

转载自juejin.im/post/5db31dc16fb9a02050547aff
今日推荐