Article catalog
Thread synchronizer in Java Concurrent package (CountDownLatch, CyclicBarrier and Semaphore)
CountDownLatch
Using AQS implementation, the counter value is assigned to the state of AQS through the constructor, and the state field of AQS is used to represent the counter value, which is disposable and cannot be reused.
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); // Call Syn (class Sync extends AbstractQueuedSynchronizer) this.sync = new Sync(count); } Sync(int count) { setState(count); }
Core method 1: countDown
Every time the countDown counter is executed, the AQS releaseShared method CAS is called to reduce 1, countDown will not block, only CAS will fail and retry. If it is = = 0 after reduction, the waiting thread will be awakened to process the subsequent operation.
public void countDown() { // Call AQS sync.releaseShared(1); } // CountDownLatch implementation method protected boolean tryReleaseShared(int releases) { // To signal when transitioning to zero for (;;) { int c = getState(); if (c == 0) return false; int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } }
Core method 2: await
- The await method is blocked until all threads call countDown and state == 0.
- It is interrupted by other threads and an exception is thrown.
- Timeout return.
// No timeout await public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } public final void acquireSharedInterruptibly(int arg) throws InterruptedException { // Throw an interrupt exception if the thread is interrupted if (Thread.interrupted()) throw new InterruptedException(); // Try - 1, equivalent to state! = 0, put it in the condition queue to wait, and return directly if it is 0 if (tryAcquireShared(arg) < 0) // AQS Sync method doAcquireSharedInterruptibly(arg); } // A positive number indicates state == 0 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } // Timeout await public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException { // Throw an interrupt exception if the thread is interrupted if (Thread.interrupted()) throw new InterruptedException(); // If state == 0, it will directly return true, otherwise it will be put into the condition queue and wait for timeout return tryAcquireShared(arg) >= 0 || // AQS Sync method doAcquireSharedNanos(arg, nanosTimeout); }
Core method 3: getCount
Get the value of the current counter, that is, get the state value of AQS.
// Methods of CountDownLatch class public long getCount() { return sync.getCount(); } // Methods of CountDownLatch class int getCount() { // AQS Sync method return getState(); }
CyclicBarrier
Loopback barrier: when all threads call the await method, the threads will break through the barrier and continue to run downward, which can be reused (two fields are maintained internally, one party saves the set counter value, and one count calculates the current counter value). For example, after all threads have finished executing task 1, they can execute task 2. After all threads have finished executing task 2, they can execute task 3 and declare a CyclicBarrier.
Example:
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2); public static void main( String[] args ) { ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.submit(new Thread(() -> { try { System.out.println(Thread.currentThread() + "step 1"); cyclicBarrier.await(); System.out.println(Thread.currentThread() + "step 2"); cyclicBarrier.await(); System.out.println(Thread.currentThread() + "step 3"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } })); executorService.submit(new Thread(() -> { try { System.out.println(Thread.currentThread() + "step 1"); cyclicBarrier.await(); System.out.println(Thread.currentThread() + "step 2"); cyclicBarrier.await(); System.out.println(Thread.currentThread() + "step 3"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } })); System.out.println("over"); executorService.shutdown(); }
Core method 1: await
Blocking method, implement atomic update of counter by exclusive lock ReentrantLock, and realize thread synchronization by using conditional variable queue. The following conditions will be returned:
- The parties threads called the await method, that is, all threads reached the barrier point (return true).
- If another thread calls the interrupt method of the current thread to interrupt the current thread, the current thread will throw an InterruptedException exception and return.
- When the token flag of the Generation object associated with the current barrier point is set to true, a broken barrierexception exception is thrown and returned.
- Timeout return (return false).
If the counter is 0 after await is called, the thread in the wake-up condition queue will perform subsequent operations through the barrier. If it is not 0 after await, the thread will be placed in the condition queue to wait.
// Non timeout await public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen } } // Timeout await public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { return dowait(true, unit.toNanos(timeout)); } private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { // Get reentrant exclusive lock final ReentrantLock lock = this.lock; lock.lock(); try { // Check whether the current barrier is broken. If yes, a BrokenBarrierException will be thrown final Generation g = generation; if (g.broken) throw new BrokenBarrierException(); if (Thread.interrupted()) { // If the current thread is interrupted: // Break the barrier: generation.broken = true; // Reset counter: count = parties; // Wake up all waiting threads: trip.signalAll(); // And throw an interrupt exception InterruptedException breakBarrier(); throw new InterruptedException(); } // Execute here to indicate that the status is normal. Start the await operation, and set the current count - 1 // If = = 0 after - 1, set the barrier to true and reset count int index = --count; if (index == 0) { boolean ranAction = false; try { final Runnable command = barrierCommand; if (command != null) // Perform task command.run(); ranAction = true; // Activate other threads blocked by calling the await method, reset the counter and start the new generation of setting broken = false nextGeneration(); return 0; } finally { if (!ranAction) breakBarrier(); } } // Execution here indicates that you still need to wait until the interrupt, timeout or barrier is broken for (;;) { try { // No timeout if (!timed) trip.await(); // Include timeout else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { // Even if we are not interrupted, we will complete the waiting, so the interruption is regarded as "belonging" to subsequent execution. Thread.currentThread().interrupt(); } } if (g.broken) throw new BrokenBarrierException(); if (g != generation) return index; // Over time break the barrier if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { lock.unlock(); } } // Start a new generation private void nextGeneration() { // Wake up a thread blocked by a call to await trip.signalAll(); // Reset CyclicBarrier counter count = parties; // Reset Generation, broken == false generation = new Generation(); }
Core method 2: reset
public void reset() { final ReentrantLock lock = this.lock; lock.lock(); try { // Break the barrier breakBarrir(); // Start a new generation nextGeneration(); } finally { lock.unlock(); } } // Break the barrier private void breakBarrier() { // Break the barrier generation.broken = true; // Reset counter count = parties; // Wake up all waiting threads trip.signalAll(); } // Start a new generation private void nextGeneration() { // Wake up all waiting threads trip.signalAll(); // Reset counter count = parties; // Start a new generation generation = new Generation(); }
Semaphore
Semaphore semaphore semaphore is also a synchronizer in Java. Unlike CountDownLatch and CyclicBarrier, its internal counter is increasing. When semaphore is initialized, you can specify an initial value. However, you do not need to know the number of threads that need to be synchronized, but you need to specify the number of threads that need to be synchronized when you call the acquire method where synchronization is needed.
Core method 1: construction
// Default unfairness public Semaphore(int permits) { sync = new NonfairSync(permits); } // Can specify whether it is fair public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); } // Internally, AQS is called to set state value, and different Sync is constructed according to fair Sync(int permits) { setState(permits); }
Core method 2: acquire
// Nonparametric method equals acquire(1) public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); } // AQS method public final void acquireSharedInterruptibly(int arg) throws InterruptedException { // Check whether the thread is interrupted, and throw an exception if interrupted if (Thread.interrupted()) throw new InterruptedException(); // Call Semaphore implementation method tryAcquireShared if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); } // NonfairSync protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); } // Unfair lock implementation final int nonfairTryAcquireShared(int acquires) { for (;;) { // Get current signal value int available = getState(); // Compared with the parameters, the number of remaining semaphores is obtained int remaining = available - acquires; // If it is less than 0, it means that it has not been consumed. It directly returns a negative number and executes doacquireshared interruptible (ARG); it can block interrupt // Otherwise, set the status value to remaining and return the remaining value > = 0. Exit the acquire method to continue the subsequent business if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } } // FairSync protected int tryAcquireShared(int acquires) { for (;;) { // The difference with unfair lock is to determine whether the current thread is in the blocking queue head // If it is included in the queue, a negative number is returned and doacquireshared interruptible (ARG) is executed; interruptible blocking is performed if (hasQueuedPredecessors()) return -1; // Consistent with unfair realization int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
Core method 3: acquire uninterruptible
Similar to the acquire method, the difference is that this method is not affected by the interrupt. After the current thread calls acquirenterruptibly, if other threads call the interrupt method of the current thread and set the interrupt flag of the current thread, the current thread will not throw the InterruptException exception and return.
Core method 4: release
// Default release shared without parameters (1) public void release() { sync.releaseShared(1); } public void release(int permits) { if (permits < 0) throw new IllegalArgumentException(); sync.releaseShared(permits); } // Try + arg, if successful, then doReleaseShared, otherwise return false directly public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } // If CAS + release is successful, true will be returned. That is to say, either true or error will be returned. There is no false condition-_ -!!! protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (next < current) // overflow throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) return true; } } private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // If CAS succeeds, wake up the first element of the team unparkSuccessor(h); } // Continue to loop if CAS fails else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; } // Continue the loop if the head changes, otherwise exit the method directly if (h == head) break; } }
summary
Thread synchronizer is an important class about thread cooperation. First, CountDownLatch provides more flexible control through counters. As long as the counter value is detected as 0, it will continue to execute. Compared with join, the main thread will continue to run down more flexibly only after the thread has finished executing. In addition, CyclicBarrier can also achieve the effect of CountDownLatch, but the latter cannot be reused after the counter value becomes 0. The former will reset automatically after count == 0, or can manually call reset() method to reset. The former is applicable to the scenarios with unified algorithm but different input parameters. Semaphore adopts the semaphore increment strategy. At the beginning, it does not need to care about the number of synchronization threads. When the acquire method is called, it specifies the number of synchronization threads. It also provides a fair / unfair strategy to obtain semaphores.