Example of Java Using Thread Pool to Perform Multiple Tasks

  • 2021-09-05 00:06:27
  • OfStack

Directory 1 Task Type
1.1 Classes that Implement the Runnable Interface
1.2 Classes that Implement the Callable Interface
2 Thread pool types
2.1 Thread pool with fixed number of threads Fixed Thread Pool2.2 Cache thread pool Cached Thread Pool
2.3 Single Threaded Pool
2.4 Work Stealing Thread Pool
2.5 Scheduled Task Thread Pool
3 Using Thread Pool to Execute Tasks
3.1 Invoking a task without a return value
3.2 Invoking a task with a return value
4 Summary

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

1 Task type

Before using the thread pool to execute tasks, we figure out what tasks can be called by the thread pool. According to whether tasks have return values, tasks can be divided into two types, namely, task classes that implement Runnable (no parameter and no return value) and task classes that implement Callable interface (no parameter and no return value). Select the corresponding task type according to the requirements when typing code.

1.1 Classes that Implement the Runnable Interface

For multithreaded task types, the first natural thought is the class that implements Runnable interface. Runnable interface provides an abstract method run, which has no parameters and no return value. For example:


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

Or a simpler version of Java 8 and above:


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

1.2 Classes that Implement the Callable Interface

Callable, like Runnable1, has only one abstract method, but this abstract method has a return value. When implementing this interface, you need to specify the type of return value. For example:


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

2 Thread pool types

java. util. concurrent. Executors provides a series of static methods to create various thread pools. The main 1 thread pools and features are illustrated below, and the features of other non-enumerated thread pools can be derived from the following.

2.1 Thread pool with fixed number of threads Fixed Thread Pool

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, only n threads are running in this thread pool at any time. When the thread pool is in a saturated running state, the tasks submitted to the thread pool will be put into the execution queue. If the thread pool is in an unsaturated state, the thread pool will also exist until the shutdown method of ExecuteService is called, and the thread pool will not be cleared.


//  The number of threads created is 5 Gets or sets the thread pool of. 
ExecutorService executorService = Executors.newFixedThreadPool(5);

2.2 Cacheable thread pool Cached Thread Pool

The initial size of this type of thread pool is 0 threads. With the continuous submission of tasks to the pool, if there are no idle threads in the thread pool (0 threads also mean no idle threads), new threads will be created to ensure that no tasks are waiting; If there are idle threads, the idle threads are reused to execute tasks. Threads that are idle will only be cached in the thread pool for 60 seconds, and threads that have been idle for 60s will be closed and moved to the thread pool. Significant provider performance can be achieved when dealing with a large number of short-lived (officially: short-lived) asynchronous tasks.


// Create 1 Cacheable thread pool  
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 thread from beginning to end (why do you say this sentence, because it needs to be distinguished from Executors. newFixedThreadPool (1)), so it is still called "single thread pool". You can add tasks to a single-threaded pool, but only one at a time, and the tasks are executed in sequence. If an exception occurs to the previous task, the current thread is destroyed, but a new thread is created to perform the following task. These are similar to threads Fixed Thread Pool1 with only one thread. The only difference is that Executors. newFixedThreadPool (1) can modify the number of threads in it at run time, whereas Executors. newSingleThreadExecutor () can only have one thread forever.


// Create 1 Single thread pool 
ExecutorService executorService = Executors.newSingleThreadExecutor();

2.4 Work Stealing Thread Pool

If you open the source code, you will find that the essence of work stealing thread pool is ForkJoinPool, which 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 many task queues maintained. When one task queue is completed, the corresponding thread will steal the task execution from other task queues, which also means that the task start execution order is the same as the submission order. If there is a higher demand, the thread pool can be obtained directly through ForkJoinPool.


// Create 1 Work stealing thread pool, using CPU The core number is equal to that of the machine CPU Audit 
ExecutorService executorService = Executors.newWorkStealingPool();

// Create 1 Work stealing thread pool, using CPU 3  The number of threads cannot be set in the thread pool of work theft 
ExecutorService executorService2 = Executors.newWorkStealingPool(3);

2.5 Scheduled Task Thread Pool

Scheduled Task Thread Pool can perform certain tasks according to schedule, such as executing a task periodically.


//  Get 1 The size is 2 Scheduled task thread pool of 
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//  Add 1 Print the current thread information plan task, which is in the 3 Execute in seconds 
scheduledExecutorService.schedule(() -> { System.out.println(Thread.currentThread()); }, 3, TimeUnit.SECONDS);
//  Add 1 Print the current thread information plan task, which is in the 2 Is executed for the first time after seconds, and then every 5 Second execution 1 Times. If the task execution time exceeds 5 Seconds, then under 1 The second will come first 1 Execute immediately after the completion of the second execution 
scheduledExecutorService.scheduleAtFixedRate(() -> { System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS);
//  Add 1 Print the current thread information plan task, which is in the 2 Is executed first after seconds, and every time after the task executes 5 Execute in seconds 1 Times. 
scheduledExecutorService.scheduleWithFixedDelay(() -> { System.out.println(Thread.currentThread()); }, 2, 5, TimeUnit.SECONDS);
//  Clear one by one  idle  Thread of state 
scheduledExecutorService.shutdown();
//  Blocking, code no longer goes down until the thread pool is closed 
scheduledExecutorService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);

3 Using Thread Pool to Execute Tasks

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

3.1 Invoking a task without a return value

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


// Create 1 Thread pool 
ExecutorService executorService = Executors.newFixedThreadPool(3);

// Submit 1 Tasks with no return value (implementing the Runnable Interface) 
executorService.submit(()->System.out.println("Hello"));

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

If you have 1 task set, you can submit it one by one.


// Create 1 Thread pool 
ExecutorService executorService = Executors.newFixedThreadPool(3);
List<Runnable> tasks = Arrays.asList(
    ()->System.out.println("Hello"),
    ()->System.out.println("World"));

// Submit tasks one by one 
tasks.forEach(executorService::submit);

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

3.2 Invoking a task with a return value

Tasks with return values need to implement the Callable interface, which specifies the return value type at the generic location. When the submit method is called, an Future object is returned, and the return value can be obtained by the get () method of Future. Note here that when get () is called, the code blocks until the task is complete and there is a return value.


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

If you want to submit a batch of tasks, ExecutorService can not only submit one by one, but also call invokeAll1 secondary submission. The internal implementation of invokeAll actually submits tasks one by one with one loop. The value returned by invokeAll is 1 Future List.


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

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


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

Output:


World
World

In addition, when you look at the source code of ExecutorService, you find that it also provides 1 method <T> Future<T> 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 has no return value. So where does its return value come from? The problem is the one parameter that follows the submit method, and the value of this parameter is the returned value. After calling the submit method, there is one operation, and then the result parameter is returned directly.


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

4 Summary

When using multithreading to process tasks, you should choose the appropriate task type and thread pool type according to the situation. If there is no return value, the task of implementing Runnable or Callable interface can be adopted; If there is a return value, you should use the task of implementing the Callable interface, and the return value is obtained through the get method of Future. When selecting thread pool, if only one thread is used, use single thread pool or fixed capacity thread pool with capacity of 1; To handle a large number of short-live tasks, use a cacheable thread pool; If you want to execute some tasks in a planned or circular way, you can use the planned task thread pool; If the task needs to consume a large amount of CPU resources, the application work steals the thread pool.

These are the details of the example of Java using thread pool to perform multiple tasks. For more information about Java thread pool to perform tasks, please pay attention to other related articles on this site!


Related articles: