Multithreading - thread pool

Application of pooling Technology: thread pool, database connection pool, http connection pool, etc.

The idea of pooling technology is mainly to reduce the consumption of resources obtained each time and improve the utilization rate of resources.

Benefits of using thread pools:

  • Reduce resource consumption: reduce the consumption caused by thread creation and destruction by reusing the created threads.
  • Improve response speed: when a task arrives, it can be executed immediately without waiting for thread creation.
  • Management thread: thread is a scarce resource. If it is created without limitation, it will not only consume system resources, but also reduce the stability of the system. Using thread pool can make unified allocation, monitoring and tuning.

Executor framework

Green solid line arrow is inheritance, dotted line is interface implementation

The Executor interface is one of the most basic interfaces of the Executor framework. Most classes of the Executor framework directly or indirectly implement this interface.

The Executor interface has only one execute(Runnable command) method.

public void execute(Runnable r) {
    new Thread(r).start();
}

The ExecutorService interface inherits the Executor interface, which is used to manage threads.

public interface ExecutorService extends Executor {
// Request shutdown, timeout or current thread interruption, whichever occurs first, will cause blocking until all tasks are completed.
boolean awaitTermination(long timeout, TimeUnit unit);

// Execute the given task, and when all tasks are completed, return the Future list that keeps the task status and results.
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks);

// Execute the given task, when all tasks are completed or the timeout period is full, return the Future list of maintaining task status and results.
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);

// As soon as one of the tasks in the task list is completed, return immediately. And once normal or abnormal return, cancel the unfinished task.
<T> T invokeAny(Collection<? extends Callable<T>> tasks);

// Execute the given task, and return the result if a task has completed successfully (that is, no exception has been thrown) before the expiration of the given timeout period.
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);

// Returns true if this executor is closed.
boolean isShutdown();

// Returns true if all tasks have completed after shutdown. Call shutdown or shutdownNow first, otherwise isTerminated will never be true.
boolean isTerminated();

// Initiates a sequential shutdown to perform previously submitted tasks, but does not accept new tasks.
void shutdown();

// By calling interrupt to try to stop all the active tasks being executed, pause processing the waiting tasks, and return the list of waiting tasks.
List<Runnable> shutdownNow();

// Submit a task with a return value for execution, and return a Future representing the pending result of the task. The get method of the Future will return the given result upon successful completion.
<T> Future<T> submit(Callable<T> task);

Future<?> submit(Runnable task);

<T> Future<T> submit(Runnable task, T result);
}

execute and submit methods

  • Execute, execute a task, no return value.
  • Submit, submit a thread task with return value.
  • Submit (callable < T > task) can get its return value through future.get() get (block until the task is completed).
  • submit(Runnable task, T result) can indirectly obtain the return value of the thread through the passed in carrier result.

Next, the main implementation class of thread pool is ThreadPoolExecutor

ThreadPoolExecutor constructor

//Five parameter constructor
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue)
//Sixth parameter
ThreadFactory threadFactory

//Seventh parameter
RejectedExecutionHandler handler

The ThreadPoolExecutor class has four constructors. The first five parameters are required. The sixth and seventh parameters constitute three constructors with these five parameters.

int corePoolSize

  • The maximum number of core threads in the thread pool. When a new thread is created in the thread pool, if the total number of current threads is less than corePoolSize, the new one is the core thread. If it exceeds corePoolSize, it will enter the task queue.
  • By default, the core thread will always live in the process pool, even if the core thread is idle. If the property allowCoreThreadTimeOut of ThreadPoolExecutor is specified to be true, the idle core threads will be destroyed after a certain time (keepAliveTime).

int maximumPoolSize

  • The maximum number of threads in this thread pool. If the wait queue is full, create a non core thread.
  • Total threads = number of core threads + number of non core threads.

long keepAliveTime

  • The idle timeout of non core threads in this thread pool is long.
  • A non core thread will be destroyed if it does not work (idle) for more than the time set by this parameter.
  • If allowCoreThreadTimeOut = true is set, it will affect the core thread.

TimeUnit unit

  • The time unit of keepAliveTime.

BlockingQueue<Runnable> workQueue

  • The task queue in the thread pool maintains the Runnable object waiting for execution.
  • When all the core threads are working, the newly added tasks will be added to the queue to wait for processing. If the queue is full, a new non core thread will execute the task.

Details of three general strategies for queues:

Submit SynchronousQueue directly

Submit tasks directly to threads without saving them. If all threads are working, a new thread is created to handle the task, so it is usually required that maximumPoolSizes be set to Integer.MAX_VALUE, that is, infinite, to avoid rejecting newly submitted tasks.

Boundless queue LinkedBlockingQueue

This will cause new tasks to wait in the queue when all the core threads are busy. The created threads will not exceed the corePoolSize, and the value of maximumPoolSize will be meaningless.

ArrayBlockingQueue

You can limit the length of the queue. When receiving a task, if it does not reach the corePoolSize value, a new thread (core thread) will execute the task. If it does, it will wait in the queue. If the queue is full, a new thread (non core thread) will execute the task. If the total number of threads reaches maximumPoolSize and the queue is full, an error will occur.

Delay queue

The task passed in must first implement the Delayed interface. When the queue receives a task, it enters the queue first. Only when the specified delay time is reached, the task will be executed.

ThreadFactory threadFactory

It is an interface used to create a new thread. When it is new, it needs to implement its Thread newThread(Runnable r) method. Generally, it is not used.

RejectedExecutionHandler handler

It is used to throw exceptions. It is not used in general.

  • ThreadPoolExecutor.AbortPolicy() throw java.util.concurrent.RejectedExecutionException exception, default.
  • ThreadPoolExecutor.CallerRunsPolicy() retry adding the current task, and he will automatically call the execute() method repeatedly.  
  • ThreadPoolExecutor.DiscardOldestPolicy() discard old tasks
  • ThreadPoolExecutor.DiscardPolicy() discard the current task.  

How to configure thread pool

CPU intensive tasks

The number of CPU cores + 1, CPU intensive tasks make CPU utilization very high, and the time occupied by memory, hard disk and network is less than the time calculated by CPU itself. In this case, it is necessary to configure as small a thread as possible to avoid frequent switching between threads consuming resources.  

IO intensive tasks

2 * number of CPU cores, the CPU utilization rate of O-Intensive tasks is not high. When a thread sends a request, it can block waiting because it does not occupy CPU resources, so it can let the CPU have other threads to handle other tasks while waiting for IO, and make full use of CPU time.  

Hybrid tasks
Tasks can be divided into IO intensive and CPU intensive tasks, which are then processed by different thread pools.

Thread pool execution policy

  • If the number of threads does not reach corePoolSize, create a new core thread to execute the task
  • If the number of threads reaches corePools, the task will be moved into the queue to wait
  • Queue full, new thread (non core thread) to execute task
  • When the queue is full and the bus number reaches maximumPoolSize, an exception will be thrown by RejectedExecutionHandler

Four common thread pools

CachedThreadPool cache thread pool

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

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 
    cachedThreadPool.execute(new Runnable(){public void run() {};})

characteristic:

  • The number of core threads is 0, and the number of threads is unlimited
  • If there are idle threads, the idle threads will be reused. If there are no idle threads, a new thread will be created
  • Idle threads will only wait for 60s
  • Direct submission queue

FixedThreadPool

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

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(int theads);
    fixedThreadPool.execute(new Runnable(){public void run() {};});

Features:

  • All threads are core threads, given the number at creation time
  • Recycle when thread is idle
  • Queue unbounded

Singlethreadexector single threaded thread pool

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

    ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
    singleThreadExecutor.execute(new Runnable(){public void run() {};});
    static class FinalizableDelegatedExecutorService
        extends DelegatedExecutorService {
        FinalizableDelegatedExecutorService(ExecutorService executor) {
            super(executor);
        }
        protected void finalize() {
            super.shutdown();
        }
    }

A finalize method is added to ensure the shutdown of thread pool. DelegatedExecutorService is a class that inherits AbstractExecutorService.

Differences between and newFixedThreadPool(1)

Encapsulate it into a FinalizableDelegatedExecutorService class, which is a package of ExecutorService to prevent exposing methods that should not be exposed, and then add the finalize method to ensure the closure of the thread pool.

characteristic:

  • Thread pool has only one core thread
  • Recycle when thread is idle
  • Queue unbounded

ScheduledThreadPool fixed long cycle thread pool:

ScheduledThreadPool is a pool of threads that can implement scheduled and periodic tasks.

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

 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int theads);
 scheduledThreadPool.schedule(new Runnable() {
    public void run() {
       System.out.println("Delay execution by 1 second");
    }
 }, 1, TimeUnit.SECONDS);
 
 scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
    public void run() {
       System.out.println("Every 3 seconds after 1 second delay");
    }
 }, 1, 3, TimeUnit.SECONDS);
public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

The scheduledpoolexecutor inherits from the ThreadPoolExecutor and implements the scheduledpoolexecutorservice interface, which defines the method of scheduling tasks such as schedule. ScheduledThreadPoolExecutor has two important internal classes: DelayedWorkQueue and ScheduledFutureTask. DelayedWorkQueue is a blocking queue, while ScheduledFutureTask inherits from FutureTask and implements the Delayed interface.

characteristic:

  • Given the number of threads, process tasks regularly and periodically
  • Recycle when thread is idle
  • Queue unbounded

Why can the thread pool keep the thread free and run various tasks at any time?

The conclusion is: if there is no task in the queue, the core thread will block the method of getting the task until it returns to the task.

//Important: the poll will block until it exceeds keepAliveTime or gets the task
//take will block until it gets the task
//If we don't set allowCoreThreadTimeOut specifically when there are no tasks, our core threads will always block here

Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();

So it also explains why workQueues are blockingqueues

take method of ArrayBlockingQueue

 public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;  //Lock up
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();  //When the queue is empty, the thread will be blocked until it is woken up by another thread to take out the element
        return dequeue();  //Elements in consumption
    } finally {
        lock.unlock();
    }
  }

You can see that when there are no tasks in the queue, the await method is called to suspend the thread. Await method is the method of ConditionObject, which internally calls the park method of LockSupport class to suspend the thread. You can see here.

Tags: less Database Java network

Posted on Fri, 26 Jun 2020 01:59:12 -0400 by Loriq