Thread pool basic learning

Thread pool basic learning

Why use thread pools?

Resources that are often created and destroyed and used heavily, such as threads in concurrency, have a great impact on performance.

Thread pool idea

Many threads are created in advance and put into the thread pool. They are obtained directly when used and put back into the pool after use, which can avoid frequent creation, destruction and reuse.

Benefits of thread pooling

1. Improve response speed (reduce the time to create new threads)
2. Reduce resource consumption (reuse threads in the thread pool and do not need to be created every time)
3. Easy thread management

Executor, ExecutorService interface and ThreadPoolExecutor class

Java defines the Executor interface and defines an execute() method in the interface to execute a thread task.

public interface Executor {
    void execute(Runnable command);//Execute tasks and commands without return value. It is generally used to execute Runnable
}

Then, the ExecutorService interface implements the Executor interface and performs specific thread operations, so ExecutorService is the real thread pool interface.

public interface ExecutorService extends Executor {
   ...    
       
    <T> Future<T> submit(Callable<T> task);//When executing a task, it has a return value. Generally, it executes Callable
    
    List<Runnable> shutdownNow();//Close connection pool
    
   ... 
        
}

The ThreadPoolExecutor class is the thread pool class we really use. It has four construction methods, as shown in the following figure:

You can directly look at the last construction method (reason: the most parameters are the most comprehensive)

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}
Parameter order name type meaning
1 corePoolSize int Core thread pool size
2 maximumPoolSize int Maximum thread pool size (provided that the maximum number of threads cannot be exceeded, otherwise the task can only enter the blocking queue and wait until a thread is idle)
3 keepAliveTime long Indicates the thread survival time. Except for the core thread, the newly created threads can survive. It means that once these new threads complete the task and are idle, they will be destroyed after a certain time.
4 unit TimeUnit Survival time unit
5 workQueue BlockingQueue It refers to the blocking queue of tasks. Since there may be many tasks and only a few threads, the tasks that have not been executed will enter the queue. We know that the queue is FIFO. When the thread is idle, we will take out the tasks in this way. Generally, we do not need to implement this.
6 threadFactory ThreadFactory Thread creation factory
7 handler RejectedExecutionHandler Reject policy (how to reject tasks when there are too many tasks to process)

What is the Executors class?

public class Executors extends Object

Executors is used to provide some tool methods for Executor, ExecutorService, ScheduledExecutorService, ThreadFactory and Callable classes. In other words, executors is a factory class of tool class and thread pool, which is used to create and return different types of thread pools.

Five common thread pools

1.newFixedThreadPool (fixed size thread pool)

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

corePoolSize is equal to maximumPoolSize, and both are nThreads, that is, all its threads are core threads, which is a thread pool of fixed size, and the task execution is out of order;

keepAliveTime = 0. This parameter is invalid for core threads by default, while FixedThreadPool is all core threads;

The workQueue is a LinkedBlockingQueue (unbounded blocking queue), and the maximum value of the queue is Integer.MAX_VALUE. If the task submission speed continues to exceed the processing speed of the remaining tasks, a large number of queues will be blocked. Because the queue is large, it is likely that the memory overflows before rejecting the policy.

// Case code
public class FixThreadPool {

    public static void main(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        Fix fix = new Fix();
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);
        fixedThreadPool.execute(fix);

        fixedThreadPool.shutdownNow();

    }
}

class Fix implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"Thread start");
    }
}

/* The operation results are as follows:
pool-1-thread-2 Thread start
pool-1-thread-3 Thread start
pool-1-thread-1 Thread start
 It can be seen that the size is indeed fixed, only three threads run, and the execution order is out of order
*/

2.newSingleThreadExecutor (thread pool of a single thread)

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

Take a closer look at the newSingleThreadExecutor () method. In fact, it is a thread pool with a fixed size of 1 and wrapped once through the FinalizableDelegatedExecutorService class

//Case code
public class SingleThreadExecutor {

    public static void main(String[] args) {


        ExecutorService fixedExecutorService = Executors.newFixedThreadPool(1);
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) fixedExecutorService;
        System.out.println(threadPoolExecutor.getMaximumPoolSize());
        threadPoolExecutor.setMaximumPoolSize(2);
        System.out.println(threadPoolExecutor.getMaximumPoolSize());
/* The operation results are as follows:
    1
    2
   It can be seen that the FixedThreadPool class can be transformed downward to the ThreadPoolExecutor class, and its thread pool can be reconfigured           
*/

        
/*
    fixedExecutorService2 After being wrapped by the FinalizableDelegatedExecutorService class, it cannot be successfully transformed downward. Therefore, after fixedexecutiorservice2 is fixed, it cannot be modified to achieve the real Single   
     The following statement will exception java.lang.ClassCastException at run time:
 */
      ExecutorService fixedExecutorService2 = Executors.newSingleThreadExecutor();
      ThreadPoolExecutor threadPoolExecutor2 = (ThreadPoolExecutor) fixedExecutorService2;

    }
}

3.newCachedThreadPool (cacheable thread pool)

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

corePoolSize = 0, maximumPoolSize = Integer.MAX_VALUE, that is, the number of threads is almost unlimited;

keepAliveTime = 60s, and the thread ends automatically after 60s of idle time.

The workQueue is a synchronous queue. The queue must be delivered both in and out of the queue. Because the creation of CachedThreadPool thread is unlimited and there will be no queue waiting, synchronous queue is used;

4.newScheduledThreadPool (thread pool for task scheduling)

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);//1. Click in
}


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

//3. End point
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
     Executors.defaultThreadFactory(), defaultHandler);
}

It can be seen that newScheduledThreadPool calls the construction method of ScheduledThreadPoolExecutor, and ScheduledThreadPoolExecutor inherits ThreadPoolExecutor. Whether to construct or call the construction method of its parent class.

newScheduledThreadPool creates a thread pool that can be scheduled. It can be set to execute a thread task after a given delay time or periodically.

// Case code
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);

scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
        System.out.println("Execution delayed by 3 seconds");
    }
}, 3, TimeUnit.SECONDS);


scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println("Execution is delayed by 3 seconds and executed once every 1 second");
    }
}, 3, 1, TimeUnit.SECONDS);

5. Newworksealingpool (thread pool of sufficient size)

public static ExecutorService newWorkStealingPool() {//Point entry
    return new ForkJoinPool(Runtime.getRuntime().availableProcessors(),            ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}

//End
public ForkJoinPool(int parallelism,
                    ForkJoinWorkerThreadFactory factory,
                    UncaughtExceptionHandler handler,
                    boolean asyncMode) {
    this(checkParallelism(parallelism),
         checkFactory(factory),
         handler,
         asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
         "ForkJoinPool-" + nextPoolId() + "-worker-");
    checkPermission();
}

Newworksteelingpool is a thread pool added in JDK1.8. It implements a thread pool that is different from the above four. It uses ForkJoinPool class, which is a parallel thread pool. The number of concurrent threads is passed in the parameter. There is a clear difference between this and the previous four thread pools. The first four thread pools have the number of core threads, the maximum number of threads, and so on A number of concurrent threads is used to solve the problem, and the thread pool will not guarantee the sequential execution of tasks.

Posted by chaotica on Sat, 06 Nov 2021 03:06:43 -0700