Java officially provides some practical concurrency tool classes, which can make it easy for us to control multithreading without worrying about thread safety. Skillfully using these concurrent tool classes in work can achieve twice the result with half the effort. Let's take a look at these concurrency tool classes.
1, Hashtable and ConcurrentHashMap
In the collection of Map types, we most often use HashMap, but HashMap is not thread safe. In order to ensure thread safety, we can use Hashtable, but the performance efficiency of Hashtable is relatively low. The main reason is that Hashtable ensures thread safety by locking the whole table. In order to ensure thread safety and take into account performance efficiency, Java has provided a new concurrency tool class ConcurrentHashMap after 1.5. Its use method is very simple like HashMap.
Code demonstration:
import java.util.HashMap; import java.util.Hashtable; import java.util.concurrent.ConcurrentHashMap; public class MyHashtableDemo { public static void main(String[] args) throws InterruptedException { /* HashMap, Hashtable and ConcurrentHashMap are used for testing Start two threads to add 10000 pieces of data to the same Map set (add if the key does not exist, and update if the key exists). Finally, print out the number of data in the Map set. */ //HashMap<Integer, String> hm = new HashMap<>(); //Hashtable<Integer, String> hm = new Hashtable<>(); ConcurrentHashMap<Integer, String> hm = new ConcurrentHashMap<>(); Thread t1 = new Thread(() -> { for (int i = 0; i < 10000; i++) { hm.put(i , i + "--Thread 1"); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 10000; i++) { hm.put(i , i + "--Thread 2"); } }); t1.start(); t2.start(); //Sleep for 2 seconds to ensure that both threads can run. Thread.sleep(2000); /* From the number of hm data printed out, it can be found that: When hm is a HashMap, the number of pieces printed each time is different, which indicates that HashMap is not thread safe. When hm is Hashtable and ConcurrentHashMap, the number of pieces printed each time is 10000, which is in line with the expectation. */ System.out.println(hm.size()); } }
Execute the above code using HashMap, Hashtable and ConcurrentHashMap respectively. We will find that the number of data printed by HashMap each time is random, which shows that HashMap is not thread safe. The number of data pieces printed out by Hashtable and ConcurrentHashMap each time is 10000, which is in line with expectations, indicating that they are thread safe.
There are many online materials about the performance comparison between Hashtable and ConcurrentHashMap, as well as their underlying implementation principle. Due to space limitation, it will not be demonstrated and introduced here. The conclusion is that the overall performance efficiency of ConcurrentHashMap is higher than that of Hashtable. If you need it in your work, you can use ConcurrentHashMap.
2, CountDownLatch
The usage scenario of CoutDownLatch is to let one thread wait for other threads to execute before executing. The main methods are as follows:
method | explain |
---|---|
public CountDownLatch(int count) | The number of threads passed in the constructor to wait |
public void await() | Make the current thread wait |
public void countDown() | This method is called after the target thread waiting to be executed. |
In order to more vividly introduce the use of CoutDownLatch, we assume a case scenario:
There are three children in a family. The mother makes hot dumplings for the children. After the three children finish eating the dumplings, the mother will clean up, clean up the dishes and chopsticks, wash the dishes and wipe the table. The code implementation is as follows:
import java.util.concurrent.CountDownLatch; //Child thread public class ChileThread extends Thread { private CountDownLatch countDownLatch; public ChileThread(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { //Each child eats 15 dumplings for (int i = 1; i <= 15; i++) { System.out.println(getName() + " Finished the second day " + i + " A dumpling"); } //After eating, tell your mother (every time you execute the countDown method, reduce its internal counter by 1) countDownLatch.countDown(); } } //Mother thread public class MotherThread extends Thread { private CountDownLatch countDownLatch; public MotherThread(CountDownLatch countDownLatch) { this.countDownLatch = countDownLatch; } @Override public void run() { try { //Let the mother wait and wake up automatically after all the children eat dumplings. //When the countDownLatch internal counter becomes 0, the thread waiting here will be awakened automatically. countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Mother cleans, clears up the dishes and chopsticks, washes the dishes and cleans the table"); } } //The code demonstrates the use of CountDownLatch public class MyCountDownLatchDemo { public static void main(String[] args) { //Because the mother has to wait for three children to finish their meal, that is, she has to wait for three threads //So when the CountDownLatch object is created here, the constructor passes in 3 CountDownLatch countDownLatch = new CountDownLatch(3); //Create a mother thread and pass in countDownLatch MotherThread motherThread = new MotherThread(countDownLatch); motherThread.start(); //Create the first child thread and pass in countDownLatch ChileThread1 t1 = new ChileThread1(countDownLatch); t1.setName("Child 01"); //Create the second child thread and pass in countDownLatch ChileThread2 t2 = new ChileThread2(countDownLatch); t2.setName("Child 02"); //Create the third child thread and pass in countDownLatch ChileThread3 t3 = new ChileThread3(countDownLatch); t3.setName("Child 03"); t1.start(); t2.start(); t3.start(); } } /* The result of the final run is: The mother thread will wait at the beginning. The mother thread will execute only after all three children's threads have been executed. */
3, Semaphore
The usage scenario of Semaphore is to control the number of threads executing concurrently. The main methods are as follows:
method | explain |
---|---|
public void acquire() | Obtain the license. If it is not obtained, thread blocking will be performed until it is obtained |
public void release() | Release the license and return it to Semaphore |
In order to more vividly introduce the use of Semaphore, we assume a case scenario:
A group of girls queue up to go to the bathroom. There are only three pits in the bathroom. There is an aunt outside the bathroom for coordination and management. At most three girls are allowed to enter the bathroom each time. After one or some girls in the bathroom come out, the aunt will arrange a corresponding number of other girls to enter the bathroom. The code implementation is as follows:
import java.util.concurrent.Semaphore; public class MyRunnable implements Runnable { //Toilet aunt, administrator private Semaphore semaphore = new Semaphore(3); @Override public void run() { try { //Obtain permission to enter the toilet semaphore.acquire(); System.out.println(Thread.currentThread().getName() + " Into the bathroom"); Thread.sleep(2000); //After solving the internal emergency in two seconds, the speed is still relatively fast System.out.println(Thread.currentThread().getName() + " Left the bathroom"); //After leaving the bathroom, return the permit semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } public class MySemaphoreDemo { public static void main(String[] args) { //Instantiate an object, and all threads use the same object MyRunnable mr = new MyRunnable(); for (int i = 0; i < 100; i++) { //Create 100 girls and line up to go to the bathroom new Thread(mr).start(); } } }
Let's stop here first. I hope the content of this blog can be useful to you.