Java thread pool analysis: everyone who has seen it said it was good

foreword

Mastering the thread pool is a basic requirement for back-end programmers. I believe that almost everyone will be asked about the thread pool during the job interview process. I collected several classic thread pool interview questions on the Internet, and used this as a starting point to talk about my understanding of thread pools. If there is any misunderstanding, I very much hope that you will point it out, and then let's analyze and study together.

Classic Interview Questions

  • Interview question 1: Let me talk about the thread pool of Java, how does the function of each parameter work?
  • Interview question 2: According to the internal mechanism of the thread pool, when submitting a new task, what exceptions should be considered.
  • Interview question 3: What kinds of work queues are there in the thread pool?
  • Interview question 4: Will thread pools with unbounded queues cause memory spikes?
  • Interview Question 5: Tell me about several common thread pools and usage scenarios?

thread pool concept

Thread pool: Simply understood, it is a pool that manages threads.

  • It helps us manage threads and avoid increasing the resource consumption of creating and destroying threads . Because a thread is actually an object, to create an object, it needs to go through the class loading process, to destroy an object, and it needs to go through the GC garbage collection process, all of which require resource overhead.
  • Improve responsiveness. If the task arrives, it must be much slower than taking a thread from the thread pool and re-creating a thread for execution.
  • reuse. After the thread is used up, it is put back into the pool, which can achieve the effect of reuse and save resources.

Thread pool creation

Thread pools can be created by ThreadPoolExecutor, let's take a look at its constructor:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,
   BlockingQueue<Runnable> workQueue,
   ThreadFactory threadFactory,
   RejectedExecutionHandler handler) 
复制代码

The role of several core parameters:

  • corePoolSize: The maximum number of core threads in the thread pool
  • maximumPoolSize: The maximum number of threads in the thread pool
  • keepAliveTime: the idle survival time of non-core threads in the thread pool
  • unit: thread idle survival time unit
  • workQueue: A blocking queue for storing tasks
  • threadFactory: It is used to set the factory for creating threads. You can set meaningful names for the created threads to facilitate troubleshooting.
  • handler: There are four main types of saturation strategy events in line cities.

task execution

The thread pool execution process, which corresponds to the execute() method:

  • Submit a task, when the number of core threads surviving in the thread pool is less than the number of threads corePoolSize, the thread pool will create a core thread to process the submitted task.
  • If the number of core threads in the thread pool is full, that is, the number of threads is equal to corePoolSize, a newly submitted task will be put into the task queue workQueue for execution.
  • When the number of surviving threads in the thread pool is equal to corePoolSize, and the task queue workQueue is full, it is judged whether the number of threads reaches the maximumPoolSize, that is, whether the maximum number of threads is full. If not, create a non-core thread to execute the submitted task.
  • If the current number of threads reaches the maximumPoolSize, and there are new tasks coming, the rejection policy will be used directly.

Four rejection strategies

  • AbortPolicy (throws an exception, default)
  • DiscardPolicy (discard tasks directly)
  • DiscardOldestPolicy (discard the oldest task in the queue and continue to submit the current task to the thread pool)
  • CallerRunsPolicy (handed over to the thread where the thread pool call is located for processing)

In order to describe the execution of the thread pool vividly, I use an analogy:

  • The core thread is likened to the company's regular employees
  • Non-core threads are likened to outsourced employees
  • Blocking queues are compared to demand pools
  • Submitting a task is like making a request
  • When the product raises a requirement, the formal employee (core thread) takes the requirement (executes the task) first.
  • If the regular employees are all in demand, that is, the number of core threads is full), the product will put the demand in the demand pool (blocking queue) first.
  • If the demand pool (blocking queue) is also full, but the product continues to raise demand at this time, what should I do? Then please outsource (non-core threads) to do it.
  • If all employees (with the maximum number of threads also full) have demand working, execute the rejection policy.
  • If the outsourced employee completes the requirement, it leaves the company after a period of (keepAliveTime) idle time.

OK, here we go. Interview question 1->Java thread pool, how does the function of each parameter work? Has it been solved? I think this question is answered: corePoolSize, maximumPoolSize and other parameters of the thread pool constructor, and can describe the thread pool clearly The execution process is almost the same.

Thread pool exception handling

When using the thread pool to process tasks, the task code may throw RuntimeException. After throwing the exception, the thread pool may catch it, or create a new thread to replace the abnormal thread. We may not be able to perceive that the task is abnormal, so We need to consider thread pool exceptions.

When submitting a new task, how to handle exceptions?

Let's first look at a piece of code:

       ExecutorService threadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            threadPool.submit(() -> {
                System.out.println("current thread name" + Thread.currentThread().getName());
                Object object = null;
                System.out.print("result## "+object.toString());
            });
        }

复制代码

Obviously, this code will have exceptions, let's look at the execution results

Although there is no result output, no exception is thrown, so we cannot perceive that an exception has occurred in the task, so we need to add try/catch. As shown below:

OK, the exception handling of the thread, we can directly try...catch capture.

Execution process of thread pool exec.submit(runnable)

Through debug the abnormal submit method ( it is recommended that you also go to debug to have a look, each method in the figure is where I break the point ), the main execution flow chart of the abnormal submit method:

  //构造feature对象
  /**
     * @throws RejectedExecutionException {@inheritDoc}
     * @throws NullPointerException       {@inheritDoc}
     */
    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
     protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }
     public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
       public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
    //线程池执行
     public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
               int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }
    //捕获异常
    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
复制代码

Through the above analysis, the task executed by submit can receive the thrown exception through the get method of the Future object, and then process it. Let's take a demo to see how the get method of the Future object handles exceptions, as shown below:

Two other solutions for handling thread pool exceptions

In addition to the above 1. Catch the exception in the task code try/catch, 2. Receive the thrown exception through the get method of the Future object, and then deal with the two schemes, there are the above two schemes:

3. Set the UncaughtExceptionHandler for the worker thread and handle the exception in the uncaughtException method

Let's look directly at the correct posture achieved like this:

ExecutorService threadPool = Executors.newFixedThreadPool(1, r -> {
            Thread t = new Thread(r);
            t.setUncaughtExceptionHandler(
                    (t1, e) -> {
                        System.out.println(t1.getName() + "线程抛出的异常"+e);
                    });
            return t;
           });
        threadPool.execute(()->{
            Object object = null;
            System.out.print("result## " + object.toString());
        });
复制代码

operation result:

4. Rewrite the afterExecute method of ThreadPoolExecutor to handle the passed exception reference

Here is a demo of the jdk documentation:

class ExtendedExecutor extends ThreadPoolExecutor {
    // 这可是jdk文档里面给的例子。。
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        if (t == null && r instanceof Future<?>) {
            try {
                Object result = ((Future<?>) r).get();
            } catch (CancellationException ce) {
                t = ce;
            } catch (ExecutionException ee) {
                t = ee.getCause();
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt(); // ignore/reset
            }
        }
        if (t != null)
            System.out.println(t);
    }
}}
复制代码

So, when asked about thread pool exception handling, how to answer?

Thread pool work queue

What kind of work queues are there in the thread pool?

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • DelayQueue
  • PriorityBlockingQueue
  • SynchronousQueue

ArrayBlockingQueue

ArrayBlockingQueue (bounded queue) is a bounded blocking queue implemented with an array, sorted by FIFO.

LinkedBlockingQueue

LinkedBlockingQueue (capacity queue can be set) is a blocking queue based on a linked list structure, sorting tasks according to FIFO, and the capacity can be optionally set. If not set, it will be an unbounded blocking queue with a maximum length of Integer.MAX_VALUE, and the throughput is usually high In ArrayBlockingQuene; the newFixedThreadPool thread pool uses this queue

DelayQueue

DelayQueue (delay queue) is a queue of delayed execution of a task timing period. Sort according to the specified execution time from small to large, otherwise sort according to the order of insertion into the queue. The newScheduledThreadPool thread pool uses this queue.

PriorityBlockingQueue

PriorityBlockingQueue is an unbounded blocking queue with priority;

SynchronousQueue

SynchronousQueue (synchronous queue) is a blocking queue that does not store elements. Each insertion operation must wait until another thread calls the removal operation, otherwise the insertion operation is always in a blocking state, and the throughput is usually higher than that of LinkedBlockingQuene. The newCachedThreadPool thread pool uses this queue .

For interview questions: What kinds of work queues are there in thread pools? I think, answer the above ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, etc., tell their characteristics, and use the common thread pool of the corresponding queue (for example, the newFixedThreadPool thread pool uses LinkedBlockingQueue), and expand it, you can.

Several commonly used thread pools

  • newFixedThreadPool (a thread pool with a fixed number of threads)
  • newCachedThreadPool (thread pool of cacheable threads)
  • newSingleThreadExecutor (single-threaded thread pool)
  • newScheduledThreadPool (timed and periodic execution thread pool)

newFixedThreadPool

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

Thread pool features:

  • The number of core threads is the same as the maximum number of threads
  • There is no so-called non-idle time, i.e. keepAliveTime is 0
  • The blocking queue is an unbounded queue LinkedBlockingQueue

Working Mechanism:

  • Submit a task
  • If the number of threads is less than the core thread, create a core thread to execute the task
  • If the number of threads is equal to the core threads, add the task to the LinkedBlockingQueue blocking queue
  • If the thread finishes executing the task, go to the blocking queue to fetch the task and continue execution.

example code

   ExecutorService executor = Executors.newFixedThreadPool(10);
                    for (int i = 0; i < Integer.MAX_VALUE; i++) {
                        executor.execute(()->{
                            try {
                                Thread.sleep(10000);
                            } catch (InterruptedException e) {
                                //do nothing
                            }
            });
复制代码

IDE specifies JVM parameters: -Xmx8m -Xms8m :

Running the above code will throw OOM:

Hence the interview question: Will using a thread pool with unbounded queue cause memory spikes?

Answer : Yes, newFixedThreadPool uses an unbounded blocking queue LinkedBlockingQueue. If the thread acquires a task, the execution time of the task is relatively long (for example, the above demo is set to 10 seconds), which will cause the queue of tasks to accumulate more and more, causing the machine to Memory usage keeps skyrocketing, eventually leading to OOM.

scenes to be used

FixedThreadPool is suitable for processing CPU-intensive tasks, ensuring that when the CPU is used by worker threads for a long time, as few threads are allocated as possible, that is, it is suitable for executing long-term tasks.

newCachedThreadPool

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

Thread pool features:

  • The number of core threads is 0
  • The maximum number of threads is Integer.MAX_VALUE
  • The blocking queue is SynchronousQueue
  • The idle survival time of non-core threads is 60 seconds

When the speed of submitting tasks is greater than the speed of processing tasks, each time a task is submitted, a thread is bound to be created. In extreme cases, too many threads are created, exhausting CPU and memory resources. Since threads that are idle for 60 seconds are terminated, a CachedThreadPool that remains idle for a long time will not consume any resources.

Working Mechanism

  • Submit a task
  • Because there are no core threads, tasks are added directly to the SynchronousQueue queue.
  • Determine whether there is an idle thread, and if so, take out the task for execution.
  • If there is no idle thread, create a new thread for execution.
  • The thread that has completed the task can still survive for 60 seconds. If it receives the task during this period, it can continue to live; otherwise, it is destroyed.

example code

  ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName()+"正在执行");
            });
        }
复制代码

operation result:

scenes to be used

Used to execute a large number of short-term small tasks concurrently.

newSingleThreadExecutor

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

Thread Pool Features

  • The number of core threads is 1
  • The maximum number of threads is also 1
  • The blocking queue is LinkedBlockingQueue
  • keepAliveTime is 0

Working Mechanism

  • Submit a task
  • Whether there is a thread in the thread pool, if not, create a new thread to execute the task
  • If so, add the task to the blocking queue
  • The only current thread, fetches tasks from the queue, executes one, and then continues to fetch, one person (one thread) works around the clock.

example code

  ExecutorService executor = Executors.newSingleThreadExecutor();
                for (int i = 0; i < 5; i++) {
                    executor.execute(() -> {
                        System.out.println(Thread.currentThread().getName()+"正在执行");
                    });
        }
复制代码

operation result:

scenes to be used

It is suitable for scenarios where tasks are executed serially, one task at a time.

newScheduledThreadPool

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

Thread Pool Features

  • The maximum number of threads is Integer.MAX_VALUE
  • The blocking queue is DelayedWorkQueue
  • keepAliveTime is 0
  • scheduleAtFixedRate() : execute periodically at a certain rate
  • scheduleWithFixedDelay(): execute after a certain delay

Working Mechanism

  • add a task
  • Threads in the thread pool take tasks from DelayQueue
  • The thread obtains the task whose time is greater than or equal to the current time from the DelayQueue
  • After execution, modify the time of this task to be the time to be executed next time
  • This task is put back into the DelayQueue queue

example code

    /**
    创建一个给定初始延迟的间隔性的任务,之后的下次执行时间是上一次任务从执行到结束所需要的时间+* 给定的间隔时间
    */
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleWithFixedDelay(()->{
            System.out.println("current Time" + System.currentTimeMillis());
            System.out.println(Thread.currentThread().getName()+"正在执行");
        }, 1, 3, TimeUnit.SECONDS);
复制代码

operation result:

    /**
    创建一个给定初始延迟的间隔性的任务,之后的每次任务执行时间为 初始延迟 + N * delay(间隔) 
    */
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
            scheduledExecutorService.scheduleAtFixedRate(()->{
            System.out.println("current Time" + System.currentTimeMillis());
            System.out.println(Thread.currentThread().getName()+"正在执行");
        }, 1, 3, TimeUnit.SECONDS);;
复制代码

scenes to be used

Scenarios where tasks are executed periodically, and scenarios where the number of threads needs to be limited

Back to the interview question: talk about several common thread pools and usage scenarios?

Answer these four classic thread pools : newFixedThreadPool, newSingleThreadExecutor, newCachedThreadPool, newScheduledThreadPool, thread pool characteristics, working mechanism, separate description of usage scenarios, and then analyze possible problems, such as newFixedThreadPool memory soaring problem .

thread pool status

The thread pool has these states: RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED.

   //线程池状态
   private static final int RUNNING    = -1 << COUNT_BITS;
   private static final int SHUTDOWN   =  0 << COUNT_BITS;
   private static final int STOP       =  1 << COUNT_BITS;
   private static final int TIDYING    =  2 << COUNT_BITS;
   private static final int TERMINATED =  3 << COUNT_BITS;
复制代码

The state switching diagram of the thread pool:

RUNNING

  • The thread pool in this state will receive new tasks and process tasks in the blocking queue;
  • Call the shutdown() method of the thread pool to switch to the SHUTDOWN state;
  • Call the shutdownNow() method of the thread pool to switch to the STOP state;

SHUTDOWN

  • The thread pool in this state will not receive new tasks, but will process tasks in the blocking queue;
  • The queue is empty, and the tasks executed in the thread pool are also empty, entering the TIDYING state;

STOP

  • Threads in this state will not receive new tasks, will not process tasks in the blocking queue, and will interrupt running tasks;
  • The task executed in the thread pool is empty and enters the TIDYING state;

TIDYING

  • This state indicates that all tasks have been terminated, and the number of tasks recorded is 0.
  • After terminated() is executed, enter the TERMINATED state

TERMINATED

  • This state indicates that the thread pool is completely terminated

Reference and thanks

  • Java thread pool exception handling solution: www.jianshu.com/p/30e488f4e…
  • Java thread pool www.hollischuang.com/archives/28…
  • Interview questions about thread pool www.jianshu.com/p/9710b899e…
  • Five states of thread pool blog.csdn.net/l_kanglin/a…
  • In-depth analysis of the implementation principle of java thread pool www.jianshu.com/p/87bff5cc8…


Author: little boy picking up snails
Link: https://juejin.cn/post/6844903889678893063
Source: Rare Earth Nuggets
The copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin blog.csdn.net/wdjnb/article/details/124319632