Use of thread pool (used in combination with Future/Callable)

Overview

Thread pool creation methods are always共有 7 种 (among them 6 种是通过 Executors created, 1 种是通过ThreadPoolExecutor created), but overall It can be divided into 2 categories:

  1. Thread pool created by ThreadPoolExecutor ;
  2. Thread pool created by Executors (only four types are mentioned below).

7 creation methods
The related classes related to the thread pool in Java are all in the java.util.concurrent package starting from jdk1.5. Several core classes and Interfaces include: Executor, Executors, ExecutorService, ThreadPoolExecutor, FutureTask, Callable, Runnable, etc.

Executor/ExecutorService

The relationship between Executor, Executors, ExecutorService and ThreadExecutorPool,Blog post
Insert image description here

Commonly used Executors implementations to create thread pools and use threads mainly use the classes provided in the class diagram below. The thread pool class diagram is as follows:
Insert image description here
It contains three executor interfaces:

  1. Executor: a simple interface for running new tasks
  2. ExecutorService: extends Executor and adds methods for managing the executor life cycle and task life cycle
  3. ScheduleExcutorService: extends ExecutorService to support Future and regular execution tasks

Reference blog post

Executors

It is a thread pool factory that provides many factory methods and creates the following thread pool.

// 创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor();
// 创建固定数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads);
// 创建带缓存的线程池
public static ExecutorService newCachedThreadPool();
// 创建定时调度的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
// 创建流式(fork-join)线程池,创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】
public static ExecutorService newWorkStealingPool();
//创建⼀个单线程的可以执⾏延迟任务的线程池;
Executors.newSingleThreadScheduledExecutor

Java provides four thread pools through Executors: Several ways to automatically create thread pools are all encapsulated in the Executors tool class

  1. newCachedThreadPoolCreate acacheable thread pool. If the length of the thread pool exceeds processing needs, idle threads can be flexibly recycled. If there is no recycling, then Create a new thread.
  2. newFixedThreadPool Create a fixed-length thread pool, which can control the maximum number of concurrent threads. Exceeding threads will wait in the queue.
  3. newScheduledThreadPool Create a fixed-length thread pool to support scheduled and periodic task execution.
  4. newSingleThreadExecutorCreate a single-threaded thread pool, which will only use a unique worker thread to execute tasks, ensuring that all tasks are executed in the specified order (FIFO, LIFO, priority).

Thread pool core class-ThreadPoolExecutor

ThreadPoolExecutor: The most original way to create a thread pool, it contains 7 parameters to set;

This is used to manually create a thread pool and provides several construction methods. The bottom construction method is only the following one.

/*
	corePoolSize:核心线程数,
		也是线程池中常驻的线程数,
		线程池初始化时默认是没有线程的,
		当任务来临时才开始创建线程去执行任务
	
	maximumPoolSize:最大线程数,
		在核心线程数的基础上可能会额外增加一些非核心线程,
		需要注意:只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)
	
	keepAliveTime:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,
		注意当corePoolSize=maxPoolSize时,
		keepAliveTime参数也就不起作用了(因为不存在非核心线程);
	
	unit:keepAliveTime的时间单位
	
	workQueue:用于保存任务的队列,
		等待队列,线程池中的线程数超过核心线程数时,任务将放在等待队列,
		它是一个BlockingQueue类型的对象
		可以为无界、有界、同步移交三种队列类型之一,
		当池子里的工作线程数大于corePoolSize时,
		这时新进来的任务会被放到队列中
	
	threadFactory:创建线程的工厂类,
		默认使用Executors.defaultThreadFactory(),
		也可以使用guava库的ThreadFactoryBuilder来创建
	
	handler:线程池无法继续接收任务
		队列已满且线程数达到maximunPoolSize)时的饱和策略
		取值有:
			AbortPolicy:中断抛出异常
			CallerRunsPolicy:默默丢弃任务,不进行任何通知
			DiscardOldestPolicy:丢弃掉在队列中存在时间最久的任务
			DiscardPolicy:让提交任务的线程去执行任务(对比前三种比较友好一丢丢)

*/
 public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    
    ……}

For the above parameters, the most concerned ones are workQueue, threadFactory and handler

Waiting queue-workQueue

The waiting queue is of type BlockingQueue. In theory, as long as it is a subclass of it, we can use it as a waiting queue.
The following are some blocking queues built into jdk

  1. ArrayBlockingQueue, the queue is bounded, a blocking queue implemented based on arrays
  2. LinkedBlockingQueue, the queue can be bounded or unbounded. Blocking queue based on linked list
  3. SynchronousQueue, a blocking queue that does not store elements, each insertion operation must wait until another thread calls the removal operation, otherwise the insertion operation will remain blocked. The queue is also
  4. Default queue for Executors.newCachedThreadPool()
  5. PriorityBlockingQueue, unbounded blocking queue with priority

Thread factory-threadFactory

ThreadFactory is an interface with only one method

Deny policy-handler

The rejection strategy is the action we take on the task when the thread pool is full and the queue is also full.

  1. AbortPolicy: Interrupt throws exception
  2. CallerRunsPolicy: Silently discard tasks without any notification
  3. DiscardOldestPolicy: Discard the task that has been in the queue for the longest time
  4. DiscardPolicy: Let the thread that submitted the task execute the task (compared to the first three, it is more friendly)

Each of the four strategies has its own advantages and disadvantages. The more commonly used one is DiscardPolicy. However, one drawback of this strategy is that the task execution track will not be recorded. Therefore, weoften need to implement a custom rejection strategy by implementing the RejectedExecutionHandler interface

FutureTask/Runnable

FutureTask is a cancelable asynchronous calculation. FutureTask implements the basic methods of Future
Insert image description here
Commonly used methods in the FutureTask class:Blog reference 2

  1. public boolean isCancelled() Returns true if this task was canceled before completing normally.
  2. public boolean isDone() Returns true if the task is completed.
  3. public V get() Wait for the calculation to complete before retrieving its result.
  4. public V get(long timeout, TimeUnit unit), if necessary waits up to a given time for the calculation to complete, and then retrieves its result (if available).
  5. public boolean cancel(boolean mayInterruptIfRunning), try to cancel this task.
  6. protected void set(V v): Sets the result of this future to the given value, unless this future has already been set or canceled.

Two ways to create a FutureTask:Blog reference 1

  1. Use Callable
     FutureTask<Boolean> future = new FutureTask<>(new Callable<Boolean>() {
          
          
       @Override
       public Boolean call() throws Exception {
          
          
         return true;
       }
     });
    
    
  2. Directly create a new FutureTask
    //托管给线程池处理
    Future<?> futureResult = Executors.newCachedThreadPool().submit(future);
    //托管给单独线程处理
    //FutureTask继承了Runnable接口,
    //所以它可以通过new Thread()的方式进行运行,
    //再由future变量来检索结果值或者取消任务等操作,通过线程池托管的方式也可以适用。
    new Thread(future).start();
    
    

Callable

Thread Pool

Reference blog post

newCachedThreadPool

Features:

  1. The upper limit of the number of thread pool is: Integer.MaxValue (2147483647);
  2. Thread poolThe default idle time is 60S. If it exceeds 60S, it will be removed from the thread pool;
  3. 新来任务时,先检查是否有空闲线程可使用,若无,则创建一个新线程执行任务
  4. Tasks are stored in a sync queue.
  5. 适用场景:短时异步任务
    Construction method:
public static ExecutorService newCachedThreadPool() {
    
    
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

newFixedThreadPool

Features:

  1. is used to create a reusable, fixed number of threads thread pool;
  2. When all threads in the thread pool are running, the new thread will enter the waiting state until an idle thread appears in the thread pool;
  3. When a thread exits and terminates in the middle of a task, a new thread will replace it to continue completing the unfinished tasks.
  4. Unless an explicit shutdown method is used to close a thread, the thread will always exist and resources will not be released.
  5. Tasks are stored in an unbounded blocking queue
  6. 适用场景:长期任务

Construction method:

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

newScheduledThreadPool

Features:

  1. Tasks are stored in an unbounded delay queue
  2. Applicable scene:需要定期执行或延迟执行的任务

Construction method:

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    
    
        return new ScheduledThreadPoolExecutor(corePoolSize);
}

public ScheduledThreadPoolExecutor(int corePoolSize) {
    
    
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

newSingleThreadExecutor

Features:

  1. Create a single Worker thread;
  2. The threads will be executed in sequence;
  3. Tasks are stored in an unbounded blocking queue
  4. Applicable scene:需要按照顺序执行的任务.

Construction method:

public static ExecutorService newSingleThreadExecutor() {
    
    
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
}

Commonly used methods:

public static void main(String args[]) 
{
    
    
    /*
     * 执行定时任务newScheduledThreadPool
     */
    ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
    //方法1:5秒后开始执行,每隔一秒执行一次
    /*
		scheduleAtFixedRate方法,一共四个参数,分别是:
			1.需要执行的任务task、
			2.延迟执行时间t1、
			3.每次执行任务的时间间隔t2、
			4.时间间隔单位。
		含义是:在t1时间过后,以 1次/t2 的频率来不断执行 task。
		代码中,在5秒延迟后,以 1次/1秒的频率执行 打印当前时间的任务。
	*/
    service.scheduleAtFixedRate(new ExecutorsTest2(), 5, 1, TimeUnit.SECONDS);
   
	
	//方法2:5秒后开始执行,每次任务执行完后延迟3秒后,继续执行下一次
	/*
		scheduleWithFixedDelay也是四个参数,分别是:
			1.待执行的任务Task,
			2.延迟时间t1,
			3.每次任务执行完毕后延迟t2秒后执行下次任务,
			4.延迟时间单位。
	*/
    service.scheduleWithFixedDelay(new ExecutorsTest3(), 5, 3, TimeUnit.SECONDS);
}

Configure thread pool parameters

Analyze based on the characteristics of the task.

  1. Nature of tasks: CPU-intensive, IO-intensive and mixed
  2. Task priority: high, medium, low
  3. Task execution time: long, medium, short
  4. Task dependencies: whether it depends on a database or other system resources

If the task is CPU-intensive, then we can set the number of thread pools to the number of CPUs to reduce the overhead caused by thread switching.
If the task is IO intensive, we can set the number of thread pools to be larger, such as the number of CPUs * 2.

可以通过Runtime.getRuntime().availableProcessors()来获取CPU的个数

Used in conjunction with Future/Callable

The main thread needs to know the running results of the child thread in order to determine how to perform the task. JDK1.5 will provide Callable and Future, Through them, the task execution results can be obtained after the task execution is completed.
Steps to use Callable: Reference blog post

  1. The task class implements the Callable interface
  2. Create a thread pool: ExecutorService es = Executors.newCachedThreadPool();
  3. Execute the task: chuju cj = new chuju();Future future = es.submit(cj);
  4. Get the execution result of the task in the child thread: future.get()

Future

The FutureTask class is the implementation class of the Future interface, providing specific implementation of asynchronous task operations.

The FutureTask class not only implements the Future interface, but also implements the Runnable interface, or more precisely, the FutureTask class implements the RunnableFuture interface

Reference blog
Insert image description here

When we perform a time-consuming task, we can start another thread to perform the time-consuming task asynchronously.At the same time, we can do other things. Therefore Future is also an application mode in multi-threading extract the execution results of the time-consuming task based on the future "order number" . When the work is done, we can

The main JDK classes related to the use of Future are FutureTask and Callable.

把任务提交给子线程去处理,主线程不用同步等待,当向线程池提交了一个Callable或Runnable任务时就会返回Future,用Future可以获取任务执行的返回结果

The Future interface symbolizes the result of asynchronous execution of tasks, that is, executing a time-consuming task can be executed in another thread, and then we can do other things at this time. After completing other things, we can call the Future.get() method to obtain the results. , if the asynchronous task has not ended yet, it will be blocked and waited until the asynchronous task is executed and the result is obtained.

The main methods of Future include:

  1. get() method: Returns the execution result of the task. If the task has not been executed yet, it will block until it is completed. If an exception occurs during execution, an exception will be thrown, but the main thread will not be aware of it and will not be affected. , unless the get() method is called to obtain the result, an ExecutionException will be thrown;

  2. get(long timeout, TimeUnit unit): Returns the execution result of the task within the specified time. If the timeout does not return, a TimeoutException will be thrown. At this time, the task needs to be explicitly canceled;

  3. cancel(boolean mayInterruptIfRunning): Cancel the task, the boolean type input parameter indicates whether to force interruption if the task is running;

  4. isDone(): Determines whether the task has been executed. Completion of execution does not mean that the task will be executed successfully. For example, if the task fails but is executed, or the task is interrupted but is executed, it will return true. It only indicates a state that the subsequent task will not be executed. Executed again;

  5. isCancelled(): Determine whether the task has been canceled;

Reference blog post

Reference 2

Callable

Runnable comes from jdk1.0, and Callable comes from jdk1.5, so Callable must be enhanced for the former.

Compare the run method of Runnable with the call method of Callable:Callable的call方法有返回值并可以抛出异常,而run方法没有返回值

// Runnable
public abstract void run();

// Callable
V call() throws Exception;

Basic usage of Callable

Pass FutureTask to the thread pool

Through FutureTask, hand it over to the thread pool for execution, blocking and waiting for the result to be returned.

  1. Use submit directly:callable的逻辑是在异步线程中处理的,主线程通过阻塞式接受异步线程返回的内容. To put it simply, the content executed in Callable is executed in another thread, but this thread is blocked and needs to be executed before the main thread can continue to run

        public static void main(String[] args) throws Exception {
          
          
            FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
          
          
                public String call() throws InterruptedException {
          
          
                    Thread.sleep(3000L);
                    System.out.println("当前线程:" + Thread.currentThread().getName());
                    return "hello world";
                }
            });
            ExecutorService executorService = Executors.newFixedThreadPool(1);
            System.out.println("开始时间戳为:" + System.currentTimeMillis());
            executorService.submit(futureTask);
            String result = futureTask.get();
            System.out.println("结束时间戳为:" + System.currentTimeMillis() + ",result = " + result);
        }
    结果打印:
    开始时间戳为:...17192
    当前线程:pool-1-thread-1
    结束时间戳为:...20202,result = hello world
    
  2. Directly using FutureTask.run(): the same as directly calling Runnable, it is always executed by the main thread

        public static void main(String[] args) throws Exception {
          
          
            FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>() {
          
          
                public String call() throws InterruptedException {
          
          
                    Thread.sleep(3000L);
                    System.out.println("当前线程:" + Thread.currentThread().getName());
                    return "hello world";
                }
            });
            System.out.println("开始时间戳为:" + System.currentTimeMillis());
            futureTask.run();
            String result = futureTask.get();
            System.out.println("结束时间戳为:" + System.currentTimeMillis() + ",result = " + result);
        }
    结果打印:
    开始时间戳为:...59794
    当前线程:main
    结束时间戳为:...62796,result = hello world
    
Executed through the thread pool and returning a Future object

This method has the same execution effect as method 1 above, that is, the return result is Future, which is actually a FutureTask.

    public static void main(String[] args) throws Exception {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        System.out.println("开始时间戳为:" + System.currentTimeMillis());
        Future<String> future = executorService.submit(new Callable<String>() {
    
    
            public String call() throws InterruptedException {
    
    
                Thread.sleep(3000L);
                System.out.println("当前线程:" + Thread.currentThread().getName());
                return "hello world";
            }
        });
        String result = future.get();
        System.out.println("结束时间戳为:" + System.currentTimeMillis() + ",result = " + result);
    }
结果打印:
开始时间戳为:...55569
当前线程:pool-1-thread-1
结束时间戳为:...58583,result = hello world

Principle analysis

Submit method of thread pool
    // FutureTask传入方式调用的submit方法
    public Future<?> submit(Runnable task) {
    
    
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    // Callable传入方式调用的submit方法
    public <T> Future<T> submit(Callable<T> task) {
    
    
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
The future.get method blocks and waits for the asynchronous thread execution result

FutureTask内部维护了任务进行的状态,当异步任务完成时,会将状态码设置为已完成,如果发生异常,会将状态码设置成对应的异常状态码。
Insert image description here

    public V get() throws InterruptedException, ExecutionException {
    
    
        int s = state;
        if (s <= COMPLETING)
            // 如果状态还在进行中,或者刚创建,就阻塞等待
            s = awaitDone(false, 0L);
        // 调用Report,返回结果或者抛出异常
        return report(s);
    }

    private V report(int s) throws ExecutionException {
    
    
        Object x = outcome;
        if (s == NORMAL)
            // 状态正常,返回结果
            return (V)x;
        if (s >= CANCELLED)
            // 状态取消,抛出取消异常
            throw new CancellationException();
        // 抛出程序执行异常
        throw new ExecutionException((Throwable)x);
    }












Summarize

Reference blog post:

  1. Blog Post 1
  2. Blog Post 2
  3. Blog Post 3

Guess you like

Origin blog.csdn.net/yyuggjggg/article/details/125937087#comments_28564754