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:
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:
- Basic types of atomic classes: Atomic Boolean, Atomic Integer, Atomic Long
- Array Type Atomic Classes: Atomic Long Array, Atomic Integer Array, Atomic Reference Array
- 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; } }