Thread pool Executor for Java multithreading

Posted by plodos on Wed, 29 Dec 2021 03:51:06 +0100

Disadvantages of new Thread

  • Each time a new Thread creates a new object, the performance is poor.
  • Threads lack unified management. They may create unlimited new threads, compete with each other, and may occupy too many system resources, resulting in panic or OOM (out of memory)
    Memory overflow). The reason for this problem is not simply that new is a Thread, but that it may be caused by program bug s or design defects
    Caused by Thread.
  • Lack of more functions, such as more execution, periodic execution, thread interruption

Benefits of thread pooling

  • Reusing existing threads reduces the overhead of object creation and extinction, and has good performance.
  • It can effectively control the maximum number of concurrent threads, improve the utilization of system resources, and avoid excessive resource competition and blocking.
  • It provides functions such as regular execution, regular execution, single thread, concurrency control, etc.

Thread pool class diagram

In the class diagram of thread pool, we most often use the Executors at the bottom to create thread pool. The class diagram above contains an executor framework, which is a framework for scheduling and controlling asynchronous tasks according to a set of execution policies. The purpose is to provide a mechanism for separating task Submission from how tasks run. It contains three executor interfaces:

  • Executor: a simple interface to run new tasks.
  • ExecutorService: extends the Executor and adds methods to manage the Executor life cycle and task life cycle.
  • ScheduleExcutorService: extends ExecutorService to support Future and regular task execution.

Thread pool core class - ThreadPoolExecutor

Parameter Description: ThreadPoolExecutor has seven parameters in total. These seven parameters together constitute the powerful function of thread pool.

  • corePoolSize: number of core threads
  • maximumPoolSize: maximum number of threads
  • workQueue: it is very important to block the queue and store the tasks waiting to be executed, which will have a significant impact on the running process of the thread pool

When we submit a new task to the thread pool, the thread pool will determine the processing method of the task according to the number of running threads in the current pool. There are three processing methods:

1. Direct switch (SynchronusQueue)

2. The maximum number of threads that can be created by an unbounded queue is corePoolSize. In this case, the maximumPoolSize will not work. When all core threads in the thread pool are running, new task submissions will be put into the waiting queue.

3, Bounded queue (ArrayBlockingQueue) the maximum poolsize can reduce the resource consumption, but this method makes it more difficult for the thread pool to schedule threads. Because the thread pool and queue capacity are limited, we want to make the throughput and processing tasks of the thread pool reach a reasonable range, make our thread scheduling relatively simple, and reduce the resource consumption as much as possible Consumption, we need to reasonably limit these two quantities

Assignment skills:

[if you want to reduce resource consumption, including cpu utilization, operating system resource consumption, context switching overhead, etc., you can set a larger queue capacity and a smaller thread pool capacity, which will reduce the throughput of the thread pool. If our submitted tasks are often blocked, we can adjust the maximumPoolSize. If our queue capacity is small, We need to set the thread pool size larger, so that the cpu utilization will be relatively higher. However, if the capacity of the thread pool is set too large and the number of tasks is increased too much, the concurrency will increase, so the scheduling between threads is a problem to be considered. This may reduce the throughput of processing tasks.

  • keepAliveTime: the maximum time for a thread to terminate when no task is executed (when the number of threads in the thread is greater than corePoolSize, if no new task is submitted at this time, threads outside the core thread will not be destroyed immediately, but wait until keepAliveTime is exceeded)
  • Unit: the time unit of keepAliveTime
  • threadFactory: thread factory, which is used to create threads. There is a default workshop to create threads. In this way, newly created threads have the same priority. They are non Guardian threads and have their names set)
  • rejectHandler: the policy when a task is rejected (blocking queue is full) (AbortPolicy default policy throws an exception directly, CallerRunsPolicy executes the task with the thread of the caller, DiscardOldestPolicy discards the top task in the queue and executes the current task, and DiscardPolicy discards the current task directly)

 corePoolSize,maximumPoolSize,workQueue
Relationship between the three: if the number of running threads is less than the corePoolSize, a new thread is directly created to process the task. Even if other threads in the thread pool are idle. If the number of running threads is greater than corePoolSize and less than maximumPoolSize, a new thread will be created to process the task only when the workQueue is full. If corePoolSize is the same as maximumPoolSize, the thread pool size created is fixed. At this time, a new task is submitted. When the workQueue is not full, the request is put into the workQueue. Wait for the empty thread to fetch the task from the workQueue. If the workQueue is full at this time, another reject policy parameter is used to execute the reject policy.

Initialization method: four initialization methods are composed of seven parameters

 

Other methods:

Thread pool lifecycle:

 

  • running: it can accept newly submitted tasks and handle tasks in the blocking queue
  • shutdown: cannot process new tasks, but can continue to process tasks in the blocking queue
  • stop: cannot receive new tasks, nor process tasks in the queue
  • tidying: if all tasks have been terminated, the number of valid threads is 0
  • terminated: final status

Creating a thread pool using the Executor

Using the Executor, you can create four thread pools: corresponding to the four thread pool initialization methods mentioned above

1,Executors.newCachedThreadPool

Create a cacheable thread pool. If the length of the thread pool exceeds the processing needs, you can flexibly recycle idle threads. If there is no recyclable thread, create a new thread.

//Source code:
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
//usage method:
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                log.info("task:{}", index);
            }
        });
    }
    executorService.shutdown();
}

It is worth noting that the return value of newCachedThreadPool is the ExecutorService type. This type only contains basic thread pool methods, but does not contain thread monitoring related methods. Therefore, when creating a new thread with the thread pool type whose return value is ExecutorService, the specific situation should be considered.

2,newFixedThreadPool

The fixed length thread pool can the maximum number of concurrent threads ready-made, exceeding the waiting in the queue

//Source code:
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
//usage method:
public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                log.info("task:{}", index);
            }
        });
    }
    executorService.shutdown();
}

3,newSingleThreadExecutor

The single thread pool uses a single common thread to execute tasks to ensure that all tasks are executed in the specified order (FIFO, priority...)

//Source code
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}
//usage method:
public static void main(String[] args) {
    ExecutorService executorService = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        final int index = i;
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                log.info("task:{}", index);
            }
        });
    }
    executorService.shutdown();
}

4,newScheduledThreadPool

The fixed length routing pool supports the execution of scheduled and periodic tasks

//Source code:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,//super here refers to ThreadPoolExecutor
          new DelayedWorkQueue());
}
//Foundation usage:
public static void main(String[] args) {
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    executorService.schedule(new Runnable() {
        @Override
        public void run() {
            log.warn("schedule run");
        }
    }, 3, TimeUnit.SECONDS);//Execution delayed by 3 seconds
    executorService.shutdown();
}

ScheduledExecutorService provides three methods:

scheduleAtFixedRate: executes tasks at the specified rate
scheduleWithFixedDelay: executes the task with the specified delay
give an example:

executorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        log.warn("schedule run");
    }
}, 1, 3, TimeUnit.SECONDS);//Every 3 seconds after one second delay

Small extension: the operation of delaying the execution of tasks can also be implemented by the Timer class in java

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        log.warn("timer run");
    }
}, new Date(), 5 * 1000);

Topics: Multithreading