Java Concurrent Programming Learning 4: CountDownLatch, Cyclic Barrier, Semaphore, and Atomic Classes

Keywords: Programming Java

Last article Thread Synchronization Keyword and Understanding In this article, we introduce the keywords frequently used in multi-threaded synchronous collaboration. Today, we will introduce some synchronous classes and the use of atomic classes. Java provides many synchronization classes, such as CountDownLatch, Cyclic Barrier, Semaphore and so on. Here is a record of each class.

CountDownLatch

CountDownLatch is a synchronization tool class that allows one or more threads to wait until the operations of other threads have been executed. It has the following methods:

public CountDownLatch(int count);  //The parameter count is the count value
public void await() throws InterruptedException;   //The thread that calls the await() method is suspended, and it waits until count is zero to continue execution.
public boolean await(long timeout, TimeUnit unit) throws InterruptedException;  //If the count value hasn't changed to zero after waiting for a certain time, it will continue to execute.
public void countDown();  //Reduce count by 1


Count Down Latch is relatively simple. Write Demo here and you'll understand it all.

val countDownLatch = CountDownLatch(3)

fun main(args: Array<String>) {
    val thread = Thread(Runnable {
        Thread.sleep(3000)
        countDownLatch.countDown()
        System.out.println(ThreadTest.timeStamp2Date() + " Once the thread has been executed")
    })
    val thread1 = Thread(Runnable {
        Thread.sleep(1000)
        countDownLatch.countDown()
        System.out.println(ThreadTest.timeStamp2Date() + " Thread 2 has been executed")
    })
    val thread2 = Thread(Runnable {
        Thread.sleep(2000)
        countDownLatch.countDown()
        System.out.println(ThreadTest.timeStamp2Date() + " Thread 3 has been executed")
    })
    thread2.start()
    thread.start()
    thread1.start()
    countDownLatch.await()
    System.out.print(ThreadTest.timeStamp2Date() + " The main thread continues to execute")

}
//--------------------------------------
2018-11-20 14:09:37 Thread 2 has been executed
2018-11-20 14:09:38 Thread 3 has been executed
2018-11-20 14:09:39 Once the thread has been executed
2018-11-20 14:09:39 The main thread continues to execute

Because CountDownLatch exists and the count value is 3, the main thread needs to wait until the CountDownLatch internal count value is reduced to zero before execution.

CyclicBarrier

Cyclic Barrier can be called a synchronization barrier, which means that a group of threads will be blocked when they reach a barrier, and all blocked threads will not continue to execute until the last thread reaches the barrier. First look at his main methods:

    public CyclicBarrier(int parties);//Count value
    public CyclicBarrier(int parties, Runnable barrierAction);//Barrier Action denotes Runnable execution when the last thread reaches the barrier
	public int await();//Blocking until the last thread encounters a barrier
	public int await(long timeout, TimeUnit unit);//Blocking until the last thread encounters a barrier or exceeds timeout time

First, test the running code of the first constructor:


val barrier = CyclicBarrier(4)

fun main(args: Array<String>) {
    val thread1 = Thread(Runnable {
        System.out.println(ThreadTest.timeStamp2Date() + " implement thread1 Code")
        Thread.sleep(1000)
        barrier.await()
        System.out.println(ThreadTest.timeStamp2Date() + " Post-barrier execution thread1 Code")
    })
    val thread2 = Thread(Runnable {
        System.out.println(ThreadTest.timeStamp2Date() + " implement thread2 Code")
        Thread.sleep(3000)
        barrier.await()
        System.out.println(ThreadTest.timeStamp2Date() + " Post-barrier execution thread2 Code")
    })
    val thread3 = Thread(Runnable {
        System.out.println(ThreadTest.timeStamp2Date() + " implement thread3 Code")
        barrier.await()
        System.out.println(ThreadTest.timeStamp2Date() + " Post-barrier execution thread3 Code")
    })
    val thread4 = Thread(Runnable {
        System.out.println(ThreadTest.timeStamp2Date() + " implement thread4 Code")
        Thread.sleep(800)
        barrier.await()
        System.out.println(ThreadTest.timeStamp2Date() + " Post-barrier execution thread4 Code")
    })
    thread1.start()
    thread2.start()
    thread3.start()
    thread4.start()
}
//--------------------------------------
2018-11-20 14:32:18 implement thread3 Code
2018-11-20 14:32:18 implement thread1 Code
2018-11-20 14:32:18 implement thread4 Code
2018-11-20 14:32:18 implement thread2 Code
2018-11-20 14:32:21 Post-barrier execution thread2 Code
2018-11-20 14:32:21 Post-barrier execution thread4 Code
2018-11-20 14:32:21 Post-barrier execution thread1 Code
2018-11-20 14:32:21 Post-barrier execution thread3 Code

You can see that when barrier.await() is called in thread2, the barrier disappears and all threads begin to execute. Then let's look at the second construct, where the incoming barrier action randomly selects one of the threads to execute the code:


val barrier = CyclicBarrier(4, Runnable {
    System.out.println("Current thread=" + Thread.currentThread().name)
})

fun main(args: Array<String>) {
    val thread1 = Thread(Runnable {
        System.out.println(ThreadTest.timeStamp2Date() + " implement thread1 Code"+" name="+Thread.currentThread().name)
        Thread.sleep(1000)
        barrier.await()
        System.out.println(ThreadTest.timeStamp2Date() + " Post-barrier execution thread1 Code"+" name="+Thread.currentThread().name)
    })
    val thread2 = Thread(Runnable {
        System.out.println(ThreadTest.timeStamp2Date() + " implement thread2 Code"+" name="+Thread.currentThread().name)
        Thread.sleep(3000)
        barrier.await()
        System.out.println(ThreadTest.timeStamp2Date() + " Post-barrier execution thread2 Code"+" name="+Thread.currentThread().name)
    })
    val thread3 = Thread(Runnable {
        System.out.println(ThreadTest.timeStamp2Date() + " implement thread3 Code"+" name="+Thread.currentThread().name)
        barrier.await()
        System.out.println(ThreadTest.timeStamp2Date() + " Post-barrier execution thread3 Code"+" name="+Thread.currentThread().name)
    })
    val thread4 = Thread(Runnable {
        System.out.println(ThreadTest.timeStamp2Date() + " implement thread4 Code"+" name="+Thread.currentThread().name)
        Thread.sleep(800)
        barrier.await()
        System.out.println(ThreadTest.timeStamp2Date() + " Post-barrier execution thread4 Code"+" name="+Thread.currentThread().name)
    })
    thread1.start()
    thread2.start()
    thread3.start()
    thread4.start()
}
//-----------------------------------------------
2018-11-20 14:42:40 implement thread3 Code name=Thread-2
2018-11-20 14:42:40 implement thread2 Code name=Thread-1
2018-11-20 14:42:40 implement thread1 Code name=Thread-0
2018-11-20 14:42:40 implement thread4 Code name=Thread-3
//Current thread = Thread-3
2018-11-20 14:42:40 Post-barrier execution thread4 Code name=Thread-3
2018-11-20 14:42:40 Post-barrier execution thread3 Code name=Thread-2
2018-11-20 14:42:40 Post-barrier execution thread2 Code name=Thread-1
2018-11-20 14:42:40 Post-barrier execution thread1 Code name=Thread-0

Semaphore

Semaphore means semaphore. Semaphore can control the number of threads accessed at the same time, obtain a license through acquire(), wait if not, and release() releases a license. Semaphore uses scenarios where resources are limited and threads consume more.

For example, a hospital has five doctors (resources) and 20 Thread patients.

Semaphore's main methods are as follows:


 public Semaphore(int permits);//The parameter is the allowable quantity
 public Semaphore(int permits, boolean fair);//Is fair, that is, the longer the waiting time, the earlier the license is obtained?
 public void acquire() throws InterruptedException {  }     //Get a license
 public void acquire(int permits) throws InterruptedException { }    //Get permits licenses
 public void release() { }          //Release a license
 public void release(int permits) { }    //Release permits
 public boolean tryAcquire() { };    //Attempting to obtain a license returns true immediately if it succeeds and false immediately if it fails.
 public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };  //Attempting to obtain a license returns true immediately if it succeeds within a specified time, or false immediately.
 public boolean tryAcquire(int permits) { }; //Attempt to obtain permits licenses, return true immediately if successful, and false immediately if failed.
 public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //Attempt to obtain permits licenses. If successful within a specified time, return true immediately or false immediately.

As you can see, Semaphore's method is similar to ReetrantLock's method. It's also similar and easy to understand. Here's a simple example to test some api s. Interested students can test others by themselves.


val semaphore = Semaphore(3)

fun main(args: Array<String>) {

    var count = 0
    val random = Random()
    while (count < 20) {
        count++
        val thread = Thread(Runnable {
            semaphore.acquire()
            System.out.println(Thread.currentThread().name + " Getting permission. Seeing a doctor")
            synchronized(random) {
                Thread.sleep((random.nextInt(2000) + 1000).toLong())
            }
            System.out.println(Thread.currentThread().name + " Release permit after medical treatment")
            semaphore.release()
        })
        thread.start()
    }
}

//-------------------------------------------------------
Thread-0 Getting permission. Seeing a doctor
Thread-1 Getting permission. Seeing a doctor
Thread-2 Getting permission. Seeing a doctor
Thread-0 Release permit after medical treatment
Thread-3 Getting permission. Seeing a doctor
Thread-2 Release permit after medical treatment
Thread-4 Getting permission. Seeing a doctor
Thread-1 Release permit after medical treatment
Thread-5 Getting permission. Seeing a doctor
Thread-4 Release permit after medical treatment
Thread-6 Getting permission. Seeing a doctor
Thread-3 Release permit after medical treatment
Thread-7 Getting permission. Seeing a doctor
Thread-6 Release permit after medical treatment
Thread-8 Getting permission. Seeing a doctor
Thread-5 Release permit after medical treatment
Thread-9 Getting permission. Seeing a doctor
Thread-8 Release permit after medical treatment
Thread-10 Getting permission. Seeing a doctor
Thread-7 Release permit after medical treatment
Thread-11 Getting permission. Seeing a doctor
Thread-10 Release permit after medical treatment
Thread-12 Getting permission. Seeing a doctor
Thread-9 Release permit after medical treatment
Thread-13 Getting permission. Seeing a doctor
Thread-12 Release permit after medical treatment
Thread-14 Getting permission. Seeing a doctor
Thread-11 Release permit after medical treatment
Thread-15 Getting permission. Seeing a doctor
Thread-14 Release permit after medical treatment
Thread-16 Getting permission. Seeing a doctor
Thread-13 Release permit after medical treatment
Thread-17 Getting permission. Seeing a doctor
Thread-16 Release permit after medical treatment
Thread-18 Getting permission. Seeing a doctor
Thread-15 Release permit after medical treatment
Thread-19 Getting permission. Seeing a doctor
Thread-18 Release permit after medical treatment
Thread-17 Release permit after medical treatment
Thread-19 Release permit after medical treatment

Ok, there are so many introductions about synchronization classes. In addition to these Java classes, there are many other synchronization classes, such as Phaser, which are not introduced here. You can check the blog carefully, or you can see the two blogs I recommend:

https://segmentfault.com/a/1190000015979879

https://blog.csdn.net/u010739551/article/details/51083004

Atomic class

Atomic class has atomicity, which means that the execution process can not be interrupted, so updating the value of atomic class is safe in multi-threaded environment. Many atomic classes are provided in Java:

  1. Basic types of atomic classes: Atomic Boolean, Atomic Integer, Atomic Long
  2. Array Type Atomic Classes: Atomic Long Array, Atomic Integer Array, Atomic Reference Array
  3. Reference type atomic classes: Atomic Reference, Atomic Reference Updater, Atomic MarkReference

The atomic classes of the basic type correspond to the boolean, int and long types of the basic type respectively. They are basically used in the same way. Problems with Java Threads and Memory Model (JMM) i++ of basic variables is not actually an atomic operation, so we can guarantee atomic operation by using Atomic Integer. The main methods are as follows:

  public final int get();//Get the current value
  public final void set(int newValue);//Set a new value
  public final int getAndDecrement();//Get the current value first, then subtract one
   public final int getAndIncrement();//Get the current value, then add one
   ...

Atomic Integer's api is very easy to understand, and there are other APIs that can be consulted by themselves. Next is the test. Here's Demo:

val atomicInteger = AtomicInteger(0)
var value = 0;
fun main(args: Array<String>) {
    var count = 0
    while (count < 100) {
        count++
        val thread = Thread(Runnable {
            atomicInteger.incrementAndGet()
            atomicInteger.incrementAndGet()
            atomicInteger.incrementAndGet()
            atomicInteger.incrementAndGet()
            atomicInteger.incrementAndGet()
            atomicInteger.incrementAndGet()
        })
        thread.start()
    }
    count = 0
    Thread.sleep(3000)
    System.out.println("Atomic type values=$atomicInteger")
    while (count < 100) {
        count++
        val thread = Thread(Runnable {
            value++
            value++
            value++
            value++
            value++
            value++
        })
        thread.start()
    }
    Thread.sleep(3000)
    System.out.println("Basic type values=$value")
}
//Output result
//Atomic type value = 600
//Basic type value = 561


Array type atomic classes can correctly update the values of corresponding arrays in a multithreaded environment. The main methods are as follows:

 public final int length();//Returns the current array length
 public final void set(int i, int newValue);//Setting the value of the corresponding index
 public final int getAndSet(int i, int newValue);//Get the value first, then set the new value
 public final int getAndIncrement(int i);//Get the current value of the corresponding index and add one
 ...

This is the main method. Let's look at the test results below.


   private int[] values = {1, 2, 0};
   private AtomicIntegerArray mIntegerArray = new AtomicIntegerArray(values);

   public void testArray() {
       mIntegerArray.getAndIncrement(2);
       mIntegerArray.getAndIncrement(1);
       mIntegerArray.getAndIncrement(0);
   }

   public String getFinalValues() {
       return mIntegerArray.get(0) + "  " + mIntegerArray.get(1) + "  " +  mIntegerArray.get(2);
   }
	//-------
	fun main(args: Array<String>) {
		var count = 0
		val threadTest = ThreadTest()
		while (count < 100) {
			count++
			val thread = Thread(Runnable {
				threadTest.testArray()
			})
			thread.start()
		}
		Thread.sleep(3000)
		System.out.println(threadTest.finalValues)
	}
//test result
101  102  100

Reference type atomic classes provide an object reference variable that is atomic in both reading and writing. In most cases, we need to deal with more than one state variable, so we can temporarily put these variables in an atomic reference. Taking AtomicReference as an example, the main methods provided are as follows:

public final V getAndSet(V newValue);//Get the value and reset the value
public final void set(V newValue);//Set value
public final V get() ;//Get value
public final boolean compareAndSet(V expect, V update);//Comparing settings, settings return true successfully, otherwise return false. CAS is used here.
...

Simple examples are as follows:

public static AtomicReference<User> atomicUserRef = new AtomicReference<>();


   public static void main(String[] args) {

       User user = new User("conan", 15);

       atomicUserRef.set(user);

       User updateUser = new User("Shinichi", 17);

       boolean flag = atomicUserRef.compareAndSet(user, user);

       System.out.println(atomicUserRef.get().getName() + "  " + flag);

       System.out.println(atomicUserRef.get().getOld());

   }


   static class User {

       private String name;

       private int old;


       public User(String name, int old) {

           this.name = name;

           this.old = old;

       }


       public String getName() {

           return name;

       }


       public int getOld() {

           return old;

       }

   }


Reference material

Posted by DeadlySin3 on Mon, 06 May 2019 17:35:38 -0700