At the end of the exam, the average score of the class only got the second grade, the teacher in charge then asked: everyone knows the world’s first peak Qomolangma, anyone know the world’s second peak is what? Just as the teacher was going to continue to speak, he heard a voice in the corner: “K2”

preface

Article source: www.cnblogs.com/dudu19939/p… This problem is a group of friends in the manuscript, the above is the friend of the original blog link, this problem I also encountered in baidu’s interview, when the friend’s blog in this problem is to communicate with me, I also have contributed one way, hey hey, took a final look at the friend’s written, think to write well, hope share with you (PS: welcome to contribute).

The title

The following is a question I asked in my second interview with Baidu on October 11, 2018:

In Java programs, the main process needs to wait for the completion of multiple child processes before executing the subsequent code, which schemes can be implemented? In fact, we often use this requirement in our work. For example, when a user places an order for a product, the background will do a series of processing. In order to improve efficiency, each processing can be executed by a thread.

solution

1. The join method

Use Thread join() to wait for all child threads to finish executing while the main Thread is executing. Thread.join () adds the specified Thread to the current Thread and merges two alternating threads into sequential threads. For example, if thread B calls thread A’s join() method, thread B will not continue executing until thread A finishes executing.

import java.util.Vector;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Vector<Thread> vector = new Vector<>();
        for(int i=0; i<5; i++) { Thread childThread=new Thread(new Runnable() {

                @Override
                public void run(a) {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("Child thread is executed."); }}); vector.add(childThread); childThread.start(); }for(Thread thread : vector) {
            thread.join();
        }
        System.out.println("Main thread executed");
    }
Copy the code

The execution result

The child thread is executed the child thread is executed the child thread is executed the child thread is executed the main thread is executedCopy the code

2. Waiting for CountDownLatch to complete with multiple threads

The concept of CountDownLatch

CountDownLatch is a synchronization utility class that is used to coordinate synchronization between multiple threads, or for communication between threads (rather than as a mutual exclusion).

CountDownLatch enables one thread to wait for other threads to complete their work before continuing. This is implemented using a counter. The initial value of the counter is the number of threads. As each thread completes its task, the counter is reduced by one. When the counter value is 0, all threads have completed their work, and the threads waiting on CountDownLatch can resume their work. The use of the CountDownLatch

Typical use of CountDownLatch 1: a thread waits for n threads to complete before it starts running. Initialize CountDownLatch’s counter to n new CountDownLatch(n). Each time a task thread completes, the counter is reduced by 1. Countdownlatch.countdown (). The thread awaiting () on CountDownLatch is awakened. A typical application scenario is that when starting a service, the main thread waits for multiple components to load before resuming execution.

Typical use of CountDownLatch 2: Achieve maximum parallelism when multiple threads start executing tasks. Note that parallelism, not concurrency, emphasizes that multiple threads start executing at the same time. Similar to a race, multiple threads are placed at the starting point, wait for the starting gun to go off, and then run at the same time. This is done by initializing a shared CountDownLatch(1) with its counter initialized to 1. Multiple threads first coundownlatch.await() before starting the task. When the main thread calls countDown(), the counter becomes 0 and multiple threads are awakened simultaneously. The shortage of the CountDownLatch

CountDownLatch is disposable. The value of the counter can only be initialized once in the constructor, and there is no mechanism to set the value again. When CountDownLatch runs out, it cannot be used again.

import java.util.Vector;
import java.util.concurrent.CountDownLatch;

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(5);
        for(int i=0; i<5; i++) { Thread childThread=new Thread(new Runnable() {

                @Override
                public void run(a) {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("Child thread is executed."); latch.countDown(); }}); childThread.start(); } latch.await();// Block the current thread until the latch values
        System.out.println("Main thread executed"); }}Copy the code

Execution Result:

The child thread is executed the child thread is executed the child thread is executed the child thread is executed the main thread is executedCopy the code

CyclicBarrier

It is important to note that CylicBarrier is the initialization parameter that controls the synchronization of a group of threads: 5 means that there are five threads including the main thread, so there can only be four child threads, which is different from CountDownLatch.

What’s the difference between countDownLatch and cyclicBarrier? The countDownLatch can only be used once, and the CyclicBarrier method can be reset using the reset() method, so the CyclicBarrier method can handle more complex business scenarios.

I once saw an image metaphor about countDownLatch and cyclicBarrier on the Internet, that is, if you use countDownLatch in a 100-meter race, one person will send the score of one person to the judges after crossing the finish line, and 10 times in a 10-person race. If you use a CyclicBarrier, you send everyone’s data only once when the last person crosses the finish line, and that’s the difference.

package interview;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Test3 {
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        final CyclicBarrier barrier = new CyclicBarrier(5);
        for(int i=0; i<4; i++) { Thread childThread=new Thread(new Runnable() {

                @Override
                public void run(a) {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("Child thread is executed.");
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        // TODO Auto-generated catch blocke.printStackTrace(); }}}); childThread.start(); } barrier.await();// Block the current thread until the latch values
        System.out.println("Main thread executed"); }}Copy the code

Execution Result:

Child thread is executed child thread is executed child thread is executed child thread is executed main thread is executedCopy the code

4. Use yield method (note that this method has not been proven reliable in person!)

public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        for(int i=0; i<5; i++) { Thread childThread=new Thread(new Runnable() {

                @Override
                public void run(a) {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("Child thread is executed."); }}); childThread.start(); }while (Thread.activeCount() > 2) {  // Ensure that all previous threads are finished
            Thread.yield();
        }
        System.out.println("Main thread executed"); }}Copy the code

Execution Result:

The child thread is executed the child thread is executed the child thread is executed the child thread is executed main thread is executed the child thread is executedCopy the code

Why does yield have such a problem?

Causes the current thread to change from the executing (running) state to the executable (ready) state. The CPU will choose from a number of executable states, which means that the current thread can be executed again. It does not mean that another thread will be executed and that thread will not be executed next time.

Java threads have a thread.yield () method, which many translate as Thread yield. As the name implies, when a thread uses this method, it allows its own CPU to run, allowing it or another thread to run.

For example, there are a lot of people waiting in line to use the restroom. When it is their turn to use the restroom, the man suddenly says, “I’ll contest you to see who can get to the restroom first.” “And then all of them rush to the toilet at the same starting line, either grabbed by someone else or grabbed by himself. We also know that threads have a priority issue, so does the person with priority always get the toilet seat? Not necessarily. They’re just more likely to get it, or they might get it without privilege.

The essence of yield is to put the current thread back into a “queue” that grabs CPU time. (A queue simply means that all threads are on the same starting line. Not really a queue).

5.FutureTast can be used for locking, similar to CountDownLatch

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test5 {
     public static void main(String[] args) {
        MyThread td = new MyThread();
          
        //1. Perform Callable, FutureTask implementation class support, used to receive operation results.
        FutureTask<Integer> result1 = new FutureTask<>(td);
        new Thread(result1).start();
        FutureTask<Integer> result2 = new FutureTask<>(td);
        new Thread(result2).start();
        FutureTask<Integer> result3 = new FutureTask<>(td);
        new Thread(result3).start();
          
        Integer sum;
        try {
                sum = result1.get();
                sum = result2.get();
                sum = result3.get();
                // The three sum values are just for synchronization and have no practical meaning
                System.out.println(sum);
        } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
        } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
        }  //FutureTask can be used to latch something like CountDownLatch, which will not execute until all threads have executed
            
        System.out.println("Main thread executed"); }}class MyThread implements Callable<Integer> {
     
        @Override
        public Integer call(a) throws Exception {
            int sum = 0;
            Thread.sleep(1000);
            for (int i = 0; i <= 10; i++) {
                sum += i;
            }
            System.out.println("Child thread is executed.");
            returnsum; }}Copy the code

6. Use callable + future

Callable+Future is also ultimately implemented as Callable+FutureTask. Future Future = executor.submit(task);

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test6 {
    public static void main(String[] args) throws InterruptedException, ExecutionException { 
        ExecutorService executor = Executors.newCachedThreadPool(); 
        Task task = new Task(); 
        Future<Integer> future1 = executor.submit(task); 
        Future<Integer> future2 = executor.submit(task);
        // Get thread execution results for synchronization
        Integer result1 = future1.get();
        Integer result2 = future2.get();
        
        System.out.println("Main thread execution"); executor.shutdown(); }}class Task implements Callable<Integer>{ 
        @Override public Integer call(a) throws Exception { 
            int sum = 0; 
            //do something; 
            System.out.println("Child thread is executed.");
            returnsum; }}Copy the code

Execution Result:

The child thread is executed and the child thread is executed by the main threadCopy the code

Supplement:

1) Both CountDownLatch and CyclicBarrier implement waits between threads, but they have different priorities:

CountDownLatch is typically used when thread A waits for several other threads to complete their tasks before it executes.

A CyclicBarrier is typically used when a group of threads wait for each other to reach a state and then execute simultaneously.

In addition, countdownlatches are not reusable, while cyclicBarriers are.

Semaphore is similar to a lock in that it is used to control access to a set of resources.

The CountDownLatch class is actually controlled as a counter, and it’s not hard to imagine that when we initialize CountDownLatch and we pass in an int variable and then we initialize an int variable inside the class, Every time we call countDownt() we reduce the value of the variable by 1, and with await() we determine if the value of the int variable is 0. If so, all operations are complete, otherwise we wait. In fact, if you know AQS, it should be easy to imagine that you can use AQS to get synchronized state in a shared way. And that’s actually what CountDownLatch does.

References:

Blog.csdn.net/u011277123/… Blog.csdn.net/joenqc/arti… Blog.csdn.net/weixin_3855… Blog.csdn.net/LightOfMira… www.cnblogs.com/baizhanshi/…

Author Chogory experienced the autumn recruitment of 2019. He is a computer master of Harbin Institute of Technology and a Java engineer admitted to Baidu. Welcome to follow my wechat public account: Programmer Chogory.