Executor (1) ExecutorService thread pool

ExecutorService thread pool

This article mainly involves the ExecutorService in the java.util.concurrent package. ExecutorService is the implementation of thread pool in Java.

1. 1. Introduction to ExecutorService

There are two implementations of the ExecutorService interface in the Java API, which are the concrete implementation classes of the Java thread pool (for details of these two implementation classes, click here ):

  1. ThreadPoolExecutor
  2. ScheduledThreadPoolExecutor

Executor class diagram

2. ExecutorService creation

What kind of ExecutorService instance (ie thread pool) is created depends on the specific application scenario, but Java provides us with an Executors factory class, which can help us easily create various types of ExecutorService thread pools. Executors can Create the following four types of thread pools:

  1. newCachedThreadPool creates a cacheable thread pool. If the length of the thread pool exceeds the processing needs, it can flexibly recycle idle threads. If there is no reclamation, a new thread is created.
  2. newFixedThreadPool creates a fixed-length thread pool that can control the maximum number of concurrent threads, and the excess threads will wait in the queue.
  3. newScheduledThreadPool creates a fixed-length thread pool that supports scheduled and periodic task execution.
  4. newSingleThreadExecutor creates a single-threaded thread pool that only uses a single worker thread to execute tasks, ensuring that all tasks are in the specified order (FIFO, LIFO)

Note: Executors is just a factory class, and all its methods return instances of ThreadPoolExecutor and ScheduledThreadPoolExecutor.

3. Use of ExecutorService

public interface ExecutorService extends Executor {
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
        long timeout, TimeUnit unit) throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
        long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

}

public interface Executor {

    void execute(Runnable command);
}

3.1 execute(Runnable task)

ExecutorService executorService = Executors.newSingleThreadExecutor();

executorService.execute(() -> {
    System.out.println("Asynchronous task");
});

executorService.shutdown();

The problem with this method is that there is no way to know the execution result of the task. If we want to get the execution result of the task, we can pass in an instance of Callable (described below).

3.2 submit(Runnable task)

The difference between submit(Runnable) and execute(Runnable) is that the former can return a Future object. Through the returned Future object, we can check whether the submitted task is completed. Please see the following execution example:

Future future = executorService.submit(() -> {
    System.out.println("Asynchronous task");
});

future.get();  //returns null if the task has finished correctly.

The future.get() method will return a null if the task execution is complete. Note that the future.get() method will block.

3.3 submit(Callable task)

submit(Callable) is similar to submit(Runnable) in that it also returns a Future object, but in addition, submit(Callable) receives an implementation of Callable. The call() method in the Callable interface has a return value that can be Returns the execution result of the task, and the run() method in the Runnable interface is void and has no return value. See the example below:

Future<?> future = executorService.submit(() -> {
    System.out.println("Asynchronous task");
    return "binarylei";
});

System.out.println("future.get() = " + future.get());

If the task execution is completed, the future.get() method will return the execution result of the Callable task. Note that the future.get() method will block.

3.4 invokeAny(Collection tasks)

The invokeAny(...) method receives a collection of Callables. Executing this method will not return a Future, but will return the execution result of one of all Callable tasks. This method also cannot guarantee which task execution result is returned, it is one of them anyway. See the example below:

Set<Callable<String>> tasks = new HashSet<>();
for (int i = 0; i < 5; i++) {
    int num = i;
    tasks.add(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return String.valueOf(num);
        }
    });
}
System.out.println(executorService.invokeAny(tasks));

You can try to execute the above code, each execution will return a result, and the returned result is changed, it may return "Task2" or "Task1" or other.

3.5 invokeAll(Collection tasks)

invokeAll(...) is similar to invokeAny(...) and also receives a Callable collection, but after the former is executed, it will return a Future List, which corresponds to the Future object after each Callable task is executed. Here's an example of the situation:

Set<Callable<String>> tasks = new HashSet<>();
for (int i = 0; i < 5; i++) {
    int num = i;
    tasks.add(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return String.format("Task-%s", num);
        }
    });
}

List<Future<String>> futures = executorService.invokeAll(tasks);
for (int i = 0; i < futures.size(); i++) {
    System.out.println(futures.get(i).get());
}

4. ExecutorService is closed

When we finish using ExecutorService, we should close it, otherwise the threads in it will always be running.

For example, if the application is started through the main() method, after this main() exits, if the ExecutorService in the application is not closed, the application will continue to run. This happens because threads running in the ExecutorService prevent the JVM from shutting down.

  1. shutdown() : After calling the shutdown() method, the ExecutorService will not shut down immediately, but it will no longer receive new tasks, and will not shut down until all current threads are executed, and all tasks submitted before shutdown() execution will be executed. .

  2. shutdownNow() : Shut down the ExecutorService immediately, this action will skip all executing tasks and tasks that have been submitted and have not been executed. But it doesn't make any guarantees about the tasks being executed, it is possible that they will all stop, or they may complete.

  3. isShutdown(): Returns true when the shutdown() method is called.

  4. isTerminated(): Returns true when the shutdown() method is called and all submitted tasks are completed.

  5. awaitTermination(2, TimeUnit.SECONDS): When the shutdown() method is called, it returns true when all tasks are executed within 2s, and returns false when it times out.

//1. ExecutorService 不会立即关闭,直到所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。
executorService.shutdown();

//2. 关闭ExecutorService,并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。
//executorService.shutdownNow();

//3. 当调用shutdown()方法后返回为true
System.out.println(executorService.isShutdown());

//4. 当调用shutdown()方法后,并且所有提交的任务完成后返回为true
System.out.println(executorService.isTerminated());

//5. 2s 内执行守任务则返回true,否则超时返回false 
System.out.println(executorService.awaitTermination(2, TimeUnit.SECONDS));

//6. 执行完所有任务后退出
/*while (!executorService.isTerminated()) {
    System.out.println("正在执行任务");
}*/
System.out.println(executorService.isTerminated());

Record a little bit every day. Content may not be important, but habits are!

Guess you like

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