"Java Concurrent Programming Practice" reading notes - AQS, CountDownLatch and CyclicBarrier

1. AQS, AbstractQueuedSynchronizer, an abstract queue-type synchronizer, is an abstract class that uses the template method pattern. As a template class, defines a framework for multi-threaded access to shared resources. Maintain a volatile int state representing shared resources and a FIFO thread queue . The state can represent the value of Semaphore, the number of reentrants of ReentrantLock, the number of CountDownLatch, etc. The state of ReentrantReadWriteLock uses 16 bits to represent the number of write/read locks. When writing, The thread at the head of the queue acquires the lock; when reading, starting from the head node, all the reading threads before the first writing thread acquire the lock. The old version state can also represent the state of the Future, but the FutureTask is now implemented without AQS.

    The custom synchronizer only needs to realize the acquisition and release of shared resource state, including tryAcquire(int arg), tryRelease(int arg), tryAcquireShared(int arg), tryReleaseShared(int arg) methods, as for queue maintenance (failure to acquire resources into Wake the thread to dequeue when the queue/resource is released) has been implemented .

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {

	// The node class of the waiting queue, used to encapsulate the thread, will record whether the thread request is exclusive or shared
	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(Thread thread, Node mode) {
			this.nextWaiter = mode;
			this.thread = thread;
		}

		Node(Thread thread, int waitStatus) {
			this.waitStatus = waitStatus;
			this.thread = thread;
		}
	}

	// wait for the head of the queue
	private transient volatile Node head;

	// wait for the tail node of the queue
	private transient volatile Node tail;

	// abstraction of resources
	private volatile int state;

	// If the thread fails to obtain resources, it enters the waiting queue, and the node of the thread is created, using the classic volatile variable + spin CAS
	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;
				}
			}
		}
	}

	// Subclasses customize the implementation of methods such as tryAcquire(int arg) and tryRelease(int arg)
	protected boolean tryAcquire(int arg) {
		throw new UnsupportedOperationException();
	}

	protected boolean tryRelease(int arg) {
		throw new UnsupportedOperationException();
	}

	protected int tryAcquireShared(int arg) {
		throw new UnsupportedOperationException();
	}

	protected boolean tryReleaseShared(int arg) {
		throw new UnsupportedOperationException();
	}

	// first tryAcquire(), if it fails, the thread enters the waiting queue
	public final void acquire(int arg) {
		if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
			selfInterrupt();
	}

	public final void acquireInterruptibly(int arg) throws InterruptedException {
		// Throw an exception if the thread is interrupted
		if (Thread.interrupted())
			throw new InterruptedException();
		if (!tryAcquire(arg))
			doAcquireInterruptibly (arg);
	}

	// Get resources regularly
	public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
		if (Thread.interrupted())
			throw new InterruptedException();
		return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);
	}

	// Wake up the thread of the head node and dequeue
	public final boolean release(int arg) {
		if (tryRelease(arg)) {
			Node h = head;
			if (h != null && h.waitStatus != 0)
				unparkSuccessor(h);
			return true;
		}
		return false;
	}
}


2、CountDownLatch和CyclicBarrier

    There are constructors with parameter int count, CountDownLatch has countDown(), await() methods. The await() method blocks until countDown() is called count times.

    The await() method of CyclicBarrier waits for the arrival of other threads and returns the sequence number of the arrival of the thread.

    CountDownLatch is not reusable, it is one or more threads waiting for another count (specified during construction) threads. For example, an athlete is waiting for the starter to shoot. Here, multiple athletes call the await() method, and the starter is equivalent to new CountDownLatch(1) , countDown() is called when the shot is fired, await() is no longer blocked, and the athlete starts to run; CyclicBarrier is reusable, a group of threads waiting for each other, just like athletes wait for all athletes to reach the end before they start to celebrate.

    Look at the difference between the two from the source code, first look at CountDownLatch, which is implemented with an inner class that inherits AQS , and does not provide a method to restore resources to count, so it cannot be reused.

public class CountDownLatch {
	private static final class Sync extends AbstractQueuedSynchronizer {
		private static final long serialVersionUID = 4982264981922014374L;

		Sync(int count) {
			setState(count);
		}

		int getCount() {
			return getState();
		}
		
		// Override tryAcquireShared(int acquires) so that resources can only be acquired when state == 0
		protected int tryAcquireShared(int acquires) {
			return (getState() == 0) ? 1 : -1;
		}

		protected boolean tryReleaseShared(int releases) {
			// volatile + spin CAS
			for (;;) {
				int c = getState();
				if (c == 0)
					return false;
				int nextc = c - 1;
				if (compareAndSetState(c, nextc))
					return nextc == 0;
			}
		}
	}

	private final Sync sync;

	public CountDownLatch(int count) {
		if (count < 0)
			throw new IllegalArgumentException("count < 0");
		this.sync = new Sync(count);
	}
	
	// block when state != 0
	public void await() throws InterruptedException {
		sync.acquireSharedInterruptibly(1);
	}

	public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
		return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
	}

	public void countDown() {
		sync.releaseShared(1);
	}

	public long getCount() {
		return sync.getCount();
	}

	public String toString() {
		return super.toString() + "[Count = " + sync.getCount() + "]";
	}
}

    Let's look at CyclicBarrier again. ReentrantLock is used to achieve thread safety, and a count is used to save the parties specified during construction. When await(), the parties remain unchanged, count--, when resetting the barrier, just make count=parties, so it is reusable entered .

public class CyclicBarrier {

	// Ensure thread safety with ReentrantLock
	private final ReentrantLock lock = new ReentrantLock();

	private final Condition trip = lock.newCondition();

	// Specified during construction, remains unchanged
	private final int parties;

	// Initially ==parties, subtract one each time await()
	private int count;

	// reset count, start a new round of fences
	private void breakBarrier() {
		generation.broken = true;
		count = parties;
		trip.signalAll();
	}

	private int dowait(boolean timed, long nanos)
			throws InterruptedException, BrokenBarrierException, TimeoutException {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			final Generation g = generation;

			if (g.broken)
				throw new BrokenBarrierException();

			if (Thread.interrupted()) {
				breakBarrier();
				throw new InterruptedException();
			}
			// await() count minus one, count represents the number of unreached threads
			int index = --count;
			if (index == 0) {
				boolean ranAction = false;
				try {
					final Runnable command = barrierCommand;
					if (command != null)
						command.run();
					ranAction = true;
					nextGeneration();
					return 0;
				} finally {
					if (!ranAction)
						breakBarrier();
				}
			}

			for (;;) {
				try {
					if (!timed)
						trip.await();
					else if (nanos > 0L)
						nanos = trip.awaitNanos(nanos);
				} catch (InterruptedException ie) {
					if (g == generation && !g.broken) {
						breakBarrier();
						throw ie;
					} else {
						Thread.currentThread().interrupt();
					}
				}

				if (g.broken)
					throw new BrokenBarrierException();

				if (g != generation)
					return index;

				if (timed && nanos <= 0L) {
					breakBarrier();
					throw new TimeoutException();
				}
			}
		} finally {
			lock.unlock();
		}
	}
        // Specify parties and count when constructing
	public CyclicBarrier(int parties, Runnable barrierAction) {
		if (parties <= 0)
			throw new IllegalArgumentException();
		this.parties = parties;
		this.count = parties;
		this.barrierCommand = barrierAction;
	}

	public CyclicBarrier(int parties) {
		this(parties, null);
	}

	public int await() throws InterruptedException, BrokenBarrierException {
		try {
			return dowait(false, 0L);
		} catch (TimeoutException toe) {
			throw new Error(toe);
		}
	}

	public int await(long timeout, TimeUnit unit)
			throws InterruptedException, BrokenBarrierException, TimeoutException {
		return dowait(true, unit.toNanos(timeout));
	}

	public boolean isBroken() {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			return generation.broken;
		} finally {
			lock.unlock();
		}
	}

	public void reset() {
		final ReentrantLock lock = this.lock;
		lock.lock();
		try {
			// reset fence
			breakBarrier();
			nextGeneration();
		} finally {
			lock.unlock();
		}
	}
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324849212&siteId=291194637