java Concurrent programming-CountDownLatch and Cyclic Barrier differences in internal implementation and scenarios

Keywords: Programming IE Java REST

Preface

CountDownLatch and Cyclic Barrier are two important tool classes for concurrent java programming. They are widely used in many multithreaded concurrent or parallel scenarios.However, each has its own emphasis on the internal implementation and use scenarios.


Internal implementation differences

The former relies more on classical AQS mechanisms and CAS mechanisms to control internal state changes and changes of the counter itself, while the latter relies more on mechanisms such as reentrant Lock to control internal concurrency security and consistency.

 public class  {
     //Synchronization control For CountDownLatch.
     //Uses AQS state to represent count.
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

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

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            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 class CyclicBarrier {
    /**
     * Each use of the barrier is represented as a generation instance.
     * The generation changes whenever the barrier is tripped, or
     * is reset. There can be many generations associated with threads
     * using the barrier - due to the non-deterministic way the lock
     * may be allocated to waiting threads - but only one of these
     * can be active at a time (the one to which {@code count} applies)
     * and all the rest are either broken or tripped.
     * There need not be an active generation if there has been a break
     * but no subsequent reset.
     */
    private static class Generation {
        boolean broken = false;
    }

    /** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();
    /** The number of parties */
    private final int parties;
    /* The command to run when tripped */
    private final Runnable barrierCommand;
    /** The current generation */
    private Generation generation = new Generation();

    /**
     * Number of parties still waiting. Counts down from parties to 0
     * on each generation.  It is reset to parties on each new
     * generation or when broken.
     */
    private int count;

    /**
     * Updates state on barrier trip and wakes up everyone.
     * Called only while holding lock.
     */
    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

    /**
     * Sets current barrier generation as broken and wakes up everyone.
     * Called only while holding lock.
     */
    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

    /**
     * Main barrier code, covering the various policies.
     */
    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();
            }

            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }

            // loop until tripped, broken, interrupted, or timed out
            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 {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        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();
        }
    }
    ... ... //
 }


Actual Warfare - Show your own usage scenarios


/**
 *Class description: There are 5 initialization sub-threads and 6 latch deduction points. After deduction is complete, the main thread and the business thread can continue execution.
 */
public class UseCountDownLatch {
   
    static CountDownLatch latch = new CountDownLatch(6);

    /*Initialize Threads*/
    private static class InitThread implements Runnable{

        public void run() {
           System.out.println("Thread_"+Thread.currentThread().getId()
                 +" ready init work......");
            latch.countDown();
            for(int i =0;i<2;i++) {
               System.out.println("Thread_"+Thread.currentThread().getId()
                     +" ........continue do its work");
            }
        }
    }

    /*Business thread waits for latch counter to complete at 0*/
    private static class BusiThread implements Runnable{

        public void run() {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for(int i =0;i<3;i++) {
               System.out.println("BusiThread_"+Thread.currentThread().getId()
                     +" do business-----");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            public void run() {
               SleepTools.ms(1);
                System.out.println("Thread_"+Thread.currentThread().getId()
                     +" ready init work step 1st......");
                latch.countDown();
                System.out.println("begin step 2nd.......");
                SleepTools.ms(1);
                System.out.println("Thread_"+Thread.currentThread().getId()
                     +" ready init work step 2nd......");
                latch.countDown();
            }
        }).start();
        new Thread(new BusiThread()).start();
        for(int i=0;i<=3;i++){
            Thread thread = new Thread(new InitThread());
            thread.start();
        }
        latch.await();
        System.out.println("Main do ites work........");
    }
}
/**
 *Class description: There are 4 sub-threads. When they have finished their work, they hand in their own results.
 *Released uniformly to do their own thing, and the results handed in are used by other threads to stitch strings together
 */
class UseCyclicBarrier {
    private static CyclicBarrier barrier
            = new CyclicBarrier(4,new CollectThread());

    //A container that holds the results of a child thread's work
    private static ConcurrentHashMap<String,Long> resultMap
            = new ConcurrentHashMap<String,Long>();

    public static void main(String[] args) {
        for(int i=0;i<4;i++){
            Thread thread = new Thread(new SubThread());
            thread.start();
        }

    }

    /*Tasks summarized*/
    private static class CollectThread implements Runnable{

        @Override
        public void run() {
            StringBuilder result = new StringBuilder();
            for(Map.Entry<String,Long> workResult:resultMap.entrySet()){
               result.append("["+workResult.getValue()+"]");
            }
            System.out.println(" the result = "+ result);
            System.out.println("do other business........");
        }
    }

    /*Mutually Waiting Subthreads*/
    private static class SubThread implements Runnable{
        @Override
        public void run() {
           long id = Thread.currentThread().getId();
            resultMap.put(Thread.currentThread().getId()+"",id);
            try {
                   Thread.sleep(1000+id);
                   System.out.println("Thread_"+id+" ....do something ");
                barrier.await();
               Thread.sleep(1000+id);
                System.out.println("Thread_"+id+" ....do its business ");
                barrier.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}


Summary of both

1. Runable threads that summarize Cyclicbarrier results can be executed repeatedly, countdownlatch can call await() method multiple times by triggering await() method multiple times; if there is no result summary for cyclicbarrier, one call to await() is sufficient;

2. The number of threads for the New cyclicbarrier(threadCount) must match the actual number of user threads;

3. Coordination threads run simultaneously: countDownLatch coordinates the execution of worker threads and is coordinated by external threads; cyclicbarrier is coordinated between worker threads;

4. From the constructor, we can see that countDownlatch controls the number of counters running regardless of the number of threads; the number of threads passed in the cyclicbarrier construction is equal to the number of actual executing threads;

5. countDownLatch cannot be processed based on the results of running the execution sub-threads, but cyclicbarrier can;

6. As far as usage scenarios are concerned, countdownlatch is more suitable for scenarios such as a series of initialization work prior to framework loading; cyclicbarrier is more suitable for typical scenarios where multiple user threads are required to execute, and the results are summarized and recalculated;




Posted by phpconnect on Sun, 17 May 2020 12:13:23 -0700