Java example of using thread pool to perform multiple tasks

This article mainly introduces examples of Java using thread pool to perform multiple tasks, to help everyone better understand and learn to use Java, interested friends can learn about

When performing a series of unrelated asynchronous tasks with IO operations (such as downloading files), the use of multithreading can greatly improve operating efficiency. The thread pool contains a series of threads, and can manage these threads. For example: create thread, destroy thread, etc. This article will introduce how to use the thread pool in Java to perform tasks.

1 Task type

Before using the thread pool to perform tasks, we figure out what tasks can be called by the thread pool. According to whether the task has a return value, tasks can be divided into two types, namely, the task class that implements Runnable (no parameters and no return value) and the task class that implements the Callable interface (no parameters and return value). When coding, select the corresponding task type according to your needs.

1.1 A class that implements the Runnable interface

For multi-threaded task types, the first thing that naturally comes to mind is the class that implements the Runnable interface. The Runnable interface provides an abstract method run, which has no parameters and no return value. E.g:

Runnable task = new Runnable() {
    
    
  @Override
  public void run() {
    
    
    System.out.println("Execute task.");
  }
};

Or a simpler way of writing in Java 8 and above

Runnable task = ()->{
    
    
  System.out.println("Execute task.");
};

1.2 Classes that implement the Callable interface Like
Runnable, Callable has only one abstract method, but the abstract method has a return value. The type of return value needs to be formulated when implementing this interface. E.g:

Callable<String> callableTask = ()-> "finished";

Like Runnable, Callable has only one abstract method, but this abstract method has a return value. The type of return value needs to be formulated when implementing this interface. E.g:

Callable<String> callableTask = ()-> "finished";

2 Thread pool type

java.util.concurrent.Executors provides a series of static methods to create various thread pools. Some of the main thread pools and characteristics are listed below, and the characteristics of other thread pools that are not listed can be derived from the following.

2.1 Fixed Thread Pool with a fixed number of threads

As the name implies, the number of threads in this type of thread pool is fixed. If the number of threads is set to n, at most n threads in the thread pool are running at any time. When the thread pool is in a saturated running state, the tasks submitted to the thread pool will be placed in the execution queue. If the thread pool is not saturated, the thread pool will always exist until the shutdown method of ExecuteService is called, the thread pool will be cleared.

// 创建线程数量为5的线程池。
ExecutorService executorService = Executors.newFixedThreadPool(5);

2.2 Cached Thread Pool
This type of thread pool has an initial size of 0 threads. As tasks are continuously submitted to the pool, if there are no idle threads in the thread pool (0 threads also mean that there are no idle threads), then A new thread will be created to ensure that no task is waiting; if there is an idle thread, the idle state thread will be reused to perform the task. Threads that are idle will only be cached in the thread pool for 60 seconds, and threads that have been idle for 60 seconds will be shut down and removed from the thread pool. It can significantly improve program performance when dealing with a large number of short-lived (officially: short-lived) asynchronous tasks.

//创建一个可缓存的线程池 
ExecutorService executorService = Executors.newCachedThreadPool();

2.3 Single-threaded pool
This may not be called a thread pool, because there is always only one thread in it, and there is only one from start to finish (why it is said that it is different from Executors.newFixedThreadPool(1)), so Still call it "single-threaded pool handle". You can add tasks to the single-threaded pool as much as possible, but only one is executed each time, and the tasks are executed in sequence. If an exception occurs in the previous task, the current thread will be destroyed, but a new thread will be created to perform the following tasks. The above are the same as the Fixed Thread Pool with only one thread. The only difference between the two is that Executors.newFixedThreadPool(1) can modify the number of threads in it at runtime, while Executors.newSingleThreadExecutor() can always only have 1 thread.

//创建一个单线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();

2.4 Work Stealing Thread Pool
Open the source code and you will find that the work stealing thread pool is essentially ForkJoinPool. This type of thread pool makes full use of CPU multi-core processing tasks and is suitable for processing tasks that consume more CPU resources. The number of threads is not fixed, and there are multiple task queues maintained. When a task queue is completed, the corresponding thread will steal the task execution from other task queues, which also means that the task execution order is the same as the submission order . If there is a higher demand, you can directly obtain the thread pool through ForkJoinPool.

//创建一个工作窃取线程池,使用CPU核数等于机器的CPU核数
ExecutorService executorService = Executors.newWorkStealingPool();

//创建一个工作窃取线程池,使用CPU 3 个核进行计算,工作窃取线程池不能设置线程数
ExecutorService executorService2 = Executors.newWorkStealingPool(3);

2.5 Scheduled task thread pool The
scheduled task thread pool can execute certain tasks according to the plan, for example: periodically execute a certain task.

// 获取一个大小为2的计划任务线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
// 添加一个打印当前线程信息计划任务,该任务在3秒后执行
scheduledExecutorService.schedule(() -> {
    
     System.out.println(Thread.currentThread()); }, 3, TimeUnit.SECONDS);
// 添加一个打印当前线程信息计划任务,该任务在2秒后首次执行,之后每5秒执行一次。如果任务执行时间超过了5秒,则下一次将会在前一次执行完成之后立即执行
scheduledExecutorService.scheduleAtFixedRate(() -> {
    
     System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS);
// 添加一个打印当前线程信息计划任务,该任务在2秒后首次执行,之后每次在任务执行之后5秒执行下一次。
scheduledExecutorService.scheduleWithFixedDelay(() -> {
    
     System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS);
// 逐个清除 idle 状态的线程
scheduledExecutorService.shutdown();
// 阻塞,在线程池被关调之前代码不再往下走
scheduledExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);

3 Use thread pool to perform tasks

As mentioned earlier, the task types are divided into types with return value and without return value, and calls here are also divided into calls with return value and calls without return value.

3.1 Calling a task with no return value

If it is a call to a task with no return value, you can use the execute or submit method. In this case, the two are essentially the same. In order to maintain uniformity in calling tasks with return values, it is recommended to use the submit method.

//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);

//提交一个无返回值的任务(实现了Runnable接口)
executorService.submit(()->System.out.println("Hello"));

executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);

If there is a set of tasks, they can be submitted one by one.

//创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Runnable> tasks = Arrays.asList(
    ()->System.out.println("Hello"),
    ()->System.out.println("World"));

//逐个提交任务
tasks.forEach(executorService::submit);

executorService.shutdown();
executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);

3.2 Calls of tasks with return values ​​Tasks
with return values ​​need to implement the Callable interface. When implementing, specify the return value type in the generic position. When the submit method is called, a Future object is returned, and the return value can be obtained through the Future method get(). It should be noted here that the code will block when calling get() until the task is completed and there is a return value.

ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<String> future = executorService.submit(()->"Hello");
System.out.println(future.isDone());//false
String value = future.get();
System.out.println(future.isDone());//true
System.out.println(value);//Hello

If you want to submit a batch of tasks, in addition to submitting them one by one, ExecutorService can also call invokeAll for one-time submission. In fact, the internal implementation of invokeAll is to submit tasks one by one in a loop. The value returned by invokeAll is a Future List.

ExecutorService executorService = Executors.newFixedThreadPool(2);
List<Callable<String>> tasks = Arrays.asList(()->"Hello", ()->"World");
List<Future<String>> futures = executorService.invokeAll(tasks);

The invokeAny method is also very useful. The thread pool executes several tasks that implement Callable, and then returns the value of the first task that has completed execution. Other unfinished tasks will be cancelled normally without exception. The following code will not output "Hello"

ExecutorService executorService = Executors.newFixedThreadPool(2);
List<Callable<String>> tasks = Arrays.asList(
    () -> {
    
    
      Thread.sleep(500L);
      System.out.println("Hello");
      return "Hello";
    }, () -> {
    
    
      System.out.println("World");
      return "World";
    });
String s = executorService.invokeAny(tasks);
System.out.println(s);//World

Output:

World
World

In addition, when looking at the source code of ExecutorService, I found that it also provides a method Future submit(Runnable task, T result);, you can submit a task that implements the Runnable interface through this method, and then have a return value, while the run method in the Runnable interface When there is no return value. Where does its return value come from? In fact, the problem lies in a parameter after the submit method, and the parameter value is the returned value. After calling the submit method, there is an operation, and then the result parameter is returned directly.

ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future = executorService.submit(() -> System.out.println("Hello"), "World");
System.out.println(future.get());//输出:World

When using multithreading to process tasks, the appropriate task type and thread pool type should be selected according to the situation. If there is no return value, you can use the task that implements the Runnable or Callable interface; if there is a return value, you should use the task that implements the Callable interface, and the return value is obtained through the get method of Future. When selecting a thread pool, if only 1 thread is used, use a single thread pool or a fixed-capacity thread pool with a capacity of 1; to process a large number of short-live tasks, use a cacheable thread pool; if you want to plan or execute some loops Tasks can use the scheduled task thread pool; if the task needs to consume a lot of CPU resources, the application work steals the thread pool.

The above is the whole content of this article, I hope it will be helpful to everyone's study, and I hope everyone will support you

Guess you like

Origin blog.csdn.net/p1830095583/article/details/114701223