Multithreading: How to be sure that all tasks have been executed?

--------------------20180502 update---------------------------- -

Today I learned a relatively powerful class: ExecutorCompletionService, which is a jdk class that combines Executor and BlockQueue. The main purpose of its implementation is to submit task threads. After each thread task is completed in a straight line, the return value is placed in the blocking queue. Then you can return the execution result of the corresponding thread through the take() method of the blocking queue! !

So you can also write:

 

ExecutorCompletionService<String> completionService = new ExecutorCompletionService(Executors.newFixedThreadPool(5));
        for(int i=0; i<10; i++) {
            int j = i;
            completionService.submit(()-> Thread.currentThread().getName() + "------>" + j);
        }

        try {
            for(int i=0; i<10; i++) {
                Future<String> future = completionService.take();
                if(future != null) {
                    String str = future.get();
                    System.out.println(str);
                }
            }


        } catch (Exception e) {
            e.printStackTrace ();
        }
        System.out.println("---------->End");

 It can also achieve the blocking effect! (Note: There are lambda expressions to jdk1.8~)

---------------------------------------------------------------------------------------------------------

 

I wrote a blog before about writing the same sheet file in multiple threads. There are many similar scenarios. When we want to use multithreading to improve efficiency, the key issues we face are thread safety and ensuring that all tasks are completed. The blog on thread safety said that it is to ensure that the write operation to public resources is safe. For example, the add operation of List uses synchronized to wrap or directly uses thread-safe collections; the addRow of Sheet is locked, etc... And this article's The focus is " How do I ensure that all tasks are completed before moving on to the next step? ".

 

First look at the phenomenon:

 

public static void m() {
    for(int i=0; i<10; i++) {
      int j = i;
      new Thread(new Runnable() {
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName() + "------>" + j);
        }
      }).start();
    }

    System.out.println("---------->End");
  }

 This code creates 10 threads, and the task of each thread will print the name of the current thread. After running, it is found that the following results may appear (the order is not necessarily the following):

 

Thread-1------>1
------------>end

Thread-0------>0
Thread-2------>2
Thread-4------>4
Thread-3------>3
Thread-5------>5
Thread-6------>6
Thread-7------>7
Thread-8------>8
Thread-9------>9

It will be found that "----------> end" is not printed out after all threads have finished running. The mapping to the actual scene is to use multiple threads to help us work, and just download it before it is finished. One step, so there is no practical significance ( unless the multi-threaded task is asynchronous, other logic does not need to wait for it to complete to proceed ).

 

We know that multi-threaded execution tasks can be submitted by the original thread (the above code), or by the thread pool (this method is recommended for easy thread management). In order to solve the above problems, you can use the CountDownLatch counter. The initial size of the counter should be consistent with the size of the number of tasks (not related to the number of threads). Each time a task is executed, the counter is decremented by one (countDown), and the await() method will always block the main thread. The lock will not be released until the value of the counter decreases to 0, thus ensuring that all tasks are completed before proceeding to the next step.

 

First use the original thread combined with the counter to try the effect:

 

public static void m1() {
    CountDownLatch latch = new CountDownLatch(10);
    for(int i=0; i<10; i++) {
      int j = i;
      new Thread(new Runnable() {
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName() + "------>" + j);
          latch.countDown();
        }
      }).start();
    }
    try {
      latch.await();
    } catch (InterruptedException e) {
      e.printStackTrace ();
    }
    System.out.println("---------->End");
  }

 No matter how many times it is run, it will be found that "----------> end" will always be printed after all tasks of the multi-thread are executed, such as a certain result:

 

Thread-0------>0
Thread-1------>1
Thread-2------>2
Thread-3------>3
Thread-4------>4
Thread-7------>7
Thread-8------>8
Thread-9------>9
Thread-5------>5
Thread-6------>6
---------->结束

 

Then try using the thread pool combined with the counter:

 

public static void m2() {
    CountDownLatch latch = new CountDownLatch(10);
    ExecutorService es = Executors.newFixedThreadPool(5);
    for(int i=0; i<10; i++) {
      int j = i;
      es.submit(new Runnable() {
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName() + "------>" + j);
          latch.countDown();
        }
      });
    }
    try {
      latch.await();
    } catch (InterruptedException e) {
      e.printStackTrace ();
    }
    System.out.println("---------->End");
  }

 Likewise, no matter how many times it is run, "---------->end" will not proceed until all tasks are completed! For example, a print result is:

pool-1-thread-2------>1
pool-1-thread-2------>5
pool-1-thread-3------>2
pool-1-thread-1------>0
pool-1-thread-1------>8
pool-1-thread-1------>9
pool-1-thread-3------>7
pool-1-thread-4------>3
pool-1-thread-2------>6
pool-1-thread-5------>4
---------->结束

 

This fully confirms the power of the CountDownLatch counter! Let's look at a more easily overlooked way:

 

public static void m3() {
    ExecutorService es = Executors.newFixedThreadPool(5);

    List<Future<String>> list = new ArrayList<>();
    for(int i=0; i<10; i++) {
      int j = i;
      Future<String> future = es.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
          return Thread.currentThread().getName() + "------>" + j;
        }
      });
      list.add(future);
    }

    try {
      for(Future<String> future : list) {
        System.out.println(future.get());
      }
    } catch (InterruptedException e) {
      e.printStackTrace ();
    } catch (ExecutionException e) {
      e.printStackTrace ();
    }

    System.out.println("---------->End");
  }

 Needless to say, Callable can represent a thread with a return value, and Future is used to receive the returned result. The get method of Future has a blocking effect, it will block until the result is obtained. Callable&Future are generally used in conjunction with thread pools.

After running it many times, you will also find that "----------> end" always runs at the end, which also achieves the purpose.

 

When it comes to thread pool management threads, it is important to note that, for example:

 

Executors.newFixedThreadPool(40)

 

It is actually a new thread pool for the special case of corePoolSize=maximumPoolSize:

 

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

 

The static method provided by Executors creates a thread pool. Internally, a ThreadPoolExecutor is constructed, which is just a different type of thread pool.

 

The size relationship between corePoolSize and maximumPoolSize is different, and the type of task queue used is also different.

 

 

 

For some additional knowledge on multithreading and thread pools, see the following manual notes:

 

1. Some basic concepts of multithreading:

 



 

 

2. Thread pool types and the process of submitting threads:

 




 

3. Relationship diagram of common thread-related classes:

 



 

 

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326122904&siteId=291194637