To summarize the common Java development interview questions, github.com/zaiyunduan1… , continue to update ~, if you are helpful welcome Star

What is a thread pool

The basic idea of a thread pool is that it is a pool of objects that, when a program is started, creates an area of memory in which a number of (not dead) threads are stored, and the thread scheduling in the pool is handled by the pool manager. When a thread task is created, it is removed from the pool and the thread object is returned to the pool after execution. In this way, the performance cost caused by repeatedly creating thread objects is avoided and system resources are saved.

2. Benefits of using thread pools

  1. The number of threads created and destroyed is reduced, and each worker thread can be reused to perform multiple tasks.
  2. Using the thread pool can effectively control the maximum number of concurrent thread, can according to the system capacity, adjust the working line the number of threads in thread pool, prevent because consumes too much memory, but to exhaust the server (each thread takes about 1 MB of memory, thread to open, the more the greater the memory consumption, finally crash).
  3. Some simple thread management, such as: delayed execution, timed loop execution strategy, using thread pools can be well implemented

3. Main components of thread pools

A thread pool consists of the following four basic components:

  1. ThreadPool manager: used to create and manage thread pools, including creating thread pools, destroying thread pools, adding new tasks;
  2. Workthreads: Threads in a thread pool that are in a waiting state and can execute tasks in a loop.
  3. Task interface: The interface that each Task must implement for the worker thread to schedule the execution of the Task. It mainly defines the entry of the Task, the finishing work after the Task is executed, and the execution status of the Task.
  4. TaskQueue: Stores unprocessed tasks. Provide a buffer mechanism.

4, ThreadPoolExecutor class

When it comes to the thread pool, to focus on Java uitl. Concurrent. ThreadPoolExecutor, ThreadPoolExecutor thread pool at the core of a class, The UML class diagram for ThreadPoolExecutor is as follows:

new ThreadPoolExecutor(corePoolSize, maximumPoolSize,keepAliveTime, 
milliseconds,runnableTaskQueue, threadFactory,handler);
Copy the code

1. Several parameters are required to create a thread pool

  • CorePoolSize (base size of the thread pool) : When a task is submitted to the thread pool, the thread pool creates a thread to execute the task, even if other free base threads are able to execute new tasks, until the number of tasks that need to be executed exceeds the base size of the thread pool. If the thread pool’s prestartAllCoreThreads method is called, the pool creates and starts all base threads ahead of time.
  • MaximumPoolSize: The maximum number of threads allowed to be created in a thread pool. If the queue is full and the number of threads created is less than the maximum, the thread pool creates a new thread to execute the task. Note that this parameter has no effect if the unbounded task queue is used.
  • RunnableTaskQueue: A blocking queue that holds tasks waiting to be executed.
  • ThreadFactory: Used to set up a factory for creating threads. You can use the ThreadFactory to give each thread a meaningful name, which is very helpful for debugging and locating problems.
  • RejectedExecutionHandler: When both the queue and the thread pool are full, indicating that the thread pool is saturated, a policy must be adopted to handle submitted new tasks. This policy is AbortPolicy by default, indicating that an exception is thrown when a new task cannot be processed. Here are the four strategies that JDK1.5 provides. AbortPolicy: Directly throws an exception.
  • KeepAliveTime: The amount of time that a worker thread in a thread pool stays alive after it is idle. Therefore, if there are many tasks and the execution time of each task is short, you can increase the duration to improve thread utilization.
  • TimeUnit (a unit of hold time for thread activity) : The options are in DAYS, HOURS, MINUTES, MILLISECONDS, MICROSECONDS and NANOSECONDS.

2. Submit tasks to the thread pool

You can submit tasks to the thread pool using either execute() or submit(), but they are different

  • The execute() method does not return a value, so there is no way to determine whether the task was successfully executed by the thread pool
threadsPool.execute(new Runnable() {
    @Override
    public void run(a) {
    // TODO Auto-generated method stub}});Copy the code
  • The submit() method returns a future that we can use to determine whether the task was successfully executed, and the future’s get method retrieves the return value
try {
     Object s = future.get();
   } catch (InterruptedException e) {
   // Handle the interrupt exception
   } catch (ExecutionException e) {
   // Handle the exception that tasks cannot be executed
   } finally {
   // Close the thread pool
   executor.shutdown();
}
Copy the code

3. Closing the thread pool

We can turn off the thread pool by using the shutdown() or shutdownNow() methods, but they are also different

  • Shutdown works by simply setting the state of the thread pool to shutdown and then interrupting all threads that are not executing a task.
  • ShutdownNow works by iterating through worker threads in a thread pool and then interrupting them one by one by calling the thread_interrupt method, so tasks that cannot respond to interrupts may never be terminated. ShutdownNow first sets the state of the thread pool to STOP, then attempts to STOP all threads executing or suspending tasks and returns a list of tasks waiting to be executed.

4. The policy executed by ThreadPoolExecutor

    /**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@code RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState  and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, * If the current number of threads returning is smaller than the core thread pool, a new Worker will be created based on the existing thread running as the first Worker, and addWorker will automatically check the pool state and the number of workers. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back The enqueuing if * stopped, or start a new thread if there are none. * The enqueuing if * stopped, or start a new thread if there are none. If we cannot queue task, then we try to add a new * thread. If it fails, We know we are shut down or saturated * and so reject the task. If the increment fails then the current thread pool state changes or the thread pool is full * and then reject task */
        int c = ctl.get();
        // Create a new Worker if the number of current workers is smaller than the core thread pool size.
        if (workerCountOf(c) < corePoolSize) { 
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // If the current thread in CorePool is greater than or equal to CorePoolSize, add the thread to BlockingQueue.
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))//recheck prevents mutations in the thread pool state, which reject threads and prevent new threads from being added to the workQueue
                reject(command);
            else if (workerCountOf(recheck) == 0)// Both operations have addWorker operations, but if the Worker becomes 0 at workqueue. offer,
              // Then there will be no Worker to execute the new task, so add worker. addWorker(null, false);
        }
        // If the workQueue is full, the maxnum of the thread pool may not be reached yet, so try adding a Worker
        else if(! addWorker(command,false))
            reject(command);// If the number of workers reaches the upper limit, the thread is rejected
    }
Copy the code

5. Three blocking queues

BlockingQueue workQueue = null; workQueue = new ArrayBlockingQueue<>(5); // Array-based first in, first out queue, bounded workQueue = new LinkedBlockingQueue<>(); SynchronousQueue = new SynchronousQueue<>(); // Unbuffered wait queue, unbounded

  1. If the number of threads does not reach corePoolSize, a new thread (core thread) is created to execute the task
  2. When the number of threads reaches corePools, the task is moved to the queue to wait
  3. The queue is full and a new thread (non-core thread) executes the task
  4. If the queue is full and the total number of threads reaches maximumPoolSize, an exception will be thrown by (RejectedExecutionHandler)

New thread -> Core number -> Join queue -> New thread (non-core) -> Max number -> Trigger reject policy

5. Four Rejection strategies

  1. AbortPolicy: discard task and throw RejectedExecutionException anomalies
 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
Copy the code
  1. DiscardPolicy: Discards the task without throwing an exception.
  public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}Copy the code
  1. DisCardOldSetPolicy: Discards the top task in the queue and commits the new task
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
Copy the code
  1. CallerRunPolicy: The calling thread (the thread submitting the task, the main thread) handles the task
 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
Copy the code

5. Java provides four thread pools through Executors

  1. CachedThreadPool() : cacheable thread pool.
  • Unlimited number of threads
  • There are idle threads reuse idle threads, if there is no idle threads, new threads must reduce frequent creation/destruction of threads, reduce system overhead
  1. FixedThreadPool() : fixed-length thread pool.
  • Maximum number of concurrent threads that can be controlled (the number of threads executing simultaneously)
  • The exceeded thread will wait in the queue
  1. ScheduledThreadPool () :
  • Timed thread pool.
  • Supports scheduled and periodic task execution.
  1. SingleThreadExecutor() : single threaded thread pool.
  • One and only one worker thread executes the task
  • All tasks are executed in the specified order, that is, following the queue entry and exit rules

1. newCachedThreadPool

NewCachedThreadPool Creates a cacheable thread pool. If the length of the thread pool exceeds the processing requirement, free threads can be recycled, or new threads can be created

public class ThreadPoolExecutorTest1 {
	public static void main(String[] args) {
		ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
		for (int i = 0; i < 1000; i++) {
			final int index = i;
			try {
				Thread.sleep(index * 1000);
			} catch (Exception e) {
				e.printStackTrace();
			}
			cachedThreadPool.execute(new Runnable() {
				public void run() {
					System.out.println(Thread.currentThread().getName()+":"+index); }}); }}}Copy the code

2. newFixedThreadPool

NewFixedThreadPool creates a pool of threads with a fixed length that controls the maximum number of concurrent threads. The number of threads in the pool is the same as the maximum number of threads, so the number of threads is fixed

Sample code is as follows

public class ThreadPoolExecutorTest {
	public static void main(String[] args) {
		ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);// Prints three numbers every two seconds
		for (int i = 0; i < 10; i++) {
			final int index = i;
			fixedThreadPool.execute(new Runnable() {
				public void run(a) {
					try {
						System.out.println(Thread.currentThread().getName()+":"+index);
						// Three threads concurrently
						Thread.sleep(2000);
					} catch(InterruptedException e) { e.printStackTrace(); }}}); }}}Copy the code

3. newscheduledThreadPool

NewscheduledThreadPool Creates a thread pool of fixed length that supports scheduled and periodic task execution. Example code for delayed execution is as follows. The command is executed every three seconds after a delay of one second

public class ThreadPoolExecutorTest3 {
	public static void main(String[] args) {
		ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
		scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
			public void run(a) {
				System.out.println(Thread.currentThread().getName() + ": delay 1 seconds, and excute every 3 seconds"); }},1.3, TimeUnit.SECONDS);// The command is executed every three seconds after a delay of one second}}Copy the code

4. newSingleThreadExecutor

NewSingleThreadExecutor creates a single-threaded thread pool that uses only one worker thread to execute tasks, ensuring that all tasks are executed in the specified order (FIFO, LIFO, priority)

public class ThreadPoolExecutorTest4 {
	public static void main(String[] args) {
		ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 10; i++) {
			final int index = i;
			singleThreadExecutor.execute(new Runnable() {
				public void run(a) {
					try {
						System.out.println(Thread.currentThread().getName() + ":" + index);
						Thread.sleep(1000);
					} catch(InterruptedException e) { e.printStackTrace(); }}}); }}}Copy the code

Output the results in sequence, which is equivalent to executing each task in sequence. Use the JDK’s built-in monitoring tools to monitor the number of threads we create, run a non-terminating thread, create a specified number of threads, and observe


6. Thread pool parameter Settings

Parameter Settings are directly related to the system load. The following are related parameters of the system load:

  • Tasks, number of tasks to be processed per second (for system requirements)
  • Threadtasks, number of tasks per thread (for the thread itself)
  • Responsetime: indicates the maximum responsetime allowed by the system for a task. For example, the responsetime of each task should not exceed 2 seconds.

corePoolSize

If the system has tasks to process per second, each thread can process threadTasks per second. , the required number of threads is: Tasks/threadTasks, that is, the number of tasks/ threadTasks threads.

If the system has 100 to 1000 tasks per second and each thread can handle 10 tasks per note, 100/10 to 1000/10, that is, 10 to 100 threads are required. CorePoolSize should be greater than 10, based on the 8020 rule, because the number of system tasks per second is 100-1000, i.e. 80% of the time the number of system tasks per second is less than 1000 * 20% = 200. CorePoolSize can be set to 200/10 = 20.

queueCapacity

The length of the task queue depends on the number of core threads and the task response time required by the system. (corePoolSize* threadTasks) responseTime: (2010)*2=400;

maxPoolSize

When the system load reaches its maximum, the number of core threads cannot handle all tasks on time, and more threads are needed. 200 tasks per second requires 20 threads, so when 1000 tasks per second is reached, (tasks-Queuecapacity)/ threadTasks is required (1000-400)/10, which is 60 threads. MaxPoolSize can be set to 60.

If the queue length is too large, the task response time is too long. Do not use the following formula:

LinkedBlockingQueue queue = new LinkedBlockingQueue();
Copy the code

This essentially sets the queue length to integer.max_value, which will cause the number of threads to remain corePoolSize forever and never increase, and when the number of tasks jumps, so does the task response time.

keepAliveTime

When the load is low, the number of threads can be reduced. When the idle time of a thread exceeds keepAliveTime, thread resources are automatically released. By default, the thread pool stops excess threads and keeps corePoolSize at least.

allowCoreThreadTimeout

By default, the core thread does not exit. You can make the core thread exit by setting this parameter to true.

In general, it is thought that the thread pool size rule of thumb should be set like this :(where N is the number of cpus)

  • For CPU-intensive applications, set the thread pool size to N+1
  • If the application is IO intensive, set the thread pool size to 2N+1

The five states of the thread pool

  1. The thread pool has an initial state of RUNNING and is able to receive new tasks and process tasks that have been added.
  2. When the thread pool is SHUTDOWN, it does not receive new tasks but can process added tasks. When the shutdown() interface of the thread pool is called, the thread pool is run -> shutdown.
  3. When the thread pool is in the STOP state, no new tasks are received, added tasks are not processed, and ongoing tasks are interrupted. The thread pool’s shutdownNow() interface is called with (RUNNING or SHUTDOWN) -> STOP.
  4. When all tasks have terminated, the “task count” recorded by CTL is 0 and the thread pool changes to TIDYING state. The hook function terminated() is executed when the thread pool is in TIDYING state. Terminated () is empty in the ThreadPoolExecutor class and is processed if the user wants the thread pool to become TIDYING; This can be done by overloading the terminated() function.
  5. When the thread pool is SHUTDOWN, the blocking queue is empty, and the tasks executed in the thread pool are empty, SHUTDOWN -> TIDYING is called.
  6. When the thread pool is in the STOP state and the task executed in the thread pool is empty, STOP -> TIDYING is invoked. The thread pool is TERMINATED completely and becomes TERMINATED. Thread terminated() is terminated by TIDYING -> when the thread pool is in TIDYING state.

Close the thread pool

Thread pools provide two methods for shutting down thread pools: shutDown() and shutdownNow().

shutDown()

When the thread pool calls this method, the state of the thread pool immediately changes to SHUTDOWN. At this point, it can’t to add any task in the thread pool, otherwise you will be thrown RejectedExecutionException anomalies. However, the thread pool does not exit immediately until all tasks added to the pool have been processed.

shutdownNow()

This method immediately changes the state of the thread pool to STOP, and attempts to STOP all executing threads from processing tasks that are still waiting in the pool queue. Of course, it returns those that are not executed.

It attempts to terminate a Thread by calling thread.interrupt (), but as you know, this method is of limited use. It cannot interrupt the current Thread without sleep, wait, Condition, and timing locks. Therefore, ShutdownNow() does not necessarily mean that the thread pool can exit immediately; it may have to wait for all ongoing tasks to complete before exiting.

9. How to set the number of threads in various scenarios

1. How to use thread pools for businesses with high concurrency and short task execution time?

The number of threads in the thread pool can be set to CPU cores +1 to reduce thread context switching

2. How to use thread pool for business with low concurrency and long task execution time?

So you have to figure out where is the execution time going

  • If the I/O operation takes a long time, that is, THE I/O intensive task, because I/O operations do not occupy CPU, do not idle all THE CPU, you can appropriately increase the number of threads in the thread pool (2 x CPU cores), so that the CPU can process more services.

  • If the number of CPU cores is (1) +1, the number of threads in the thread pool should be set to a smaller number to reduce thread context switching

3. How to use thread pool for business with high concurrency and long execution time?

The key to solving this type of task is not the thread pool but the overall architecture design

10. Why not use JUC thread pools?

In this way, the running rules of the thread pool are more clear and the risk of resource exhaustion is avoided

1. NewFixedThreadPool and newSingleThreadExecutor

The two main problems above are that the stacked request processing queue can consume a lot of memory, or even OOM

NewCachedThreadPool and newScheduledThreadPool

The two main problems above are that the maximum number of threads is integer.max_value, which can create a very large number of threads, or even OOM.

11, problem,

1. How do non-core threads delay death?

By blocking the queue poll(), the thread is blocked and waits for some time. If no task is retrieved, the thread dies

2. Why can a thread pool keep threads running at any time?

for (;;) {
          
            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if(r ! =null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false; }}}Copy the code

In an infinite loop, the workQueue keeps fetching tasks:

  • The core thread is stuck in the workqueue.take () method, making it wait until it gets the task and then return.
  • Non-core threads will work queue.poll (keepAliveTime, timeUnit.nanoseconds). The next cycle judgment compareAndDecrementWorkerCount returns null, Worker object’s run () method the judgment of the loop body is null, the task over, and then thread is recycling system.

By blocking the queue take(), the thread waits until it gets the task

3. How to release core threads?

Set allowCoreThreadTimeOut to true. You can experiment with the following code

{
    // Allow core threads to be released with a wait time of 100 ms
    es.allowCoreThreadTimeOut(true);
    for(...) {// Add a task to the pool. The task is to print the number of threads in the current pool
        Thread.currentThread().sleep(200); }}Copy the code

The number of threads will always be 1. If allowCoreThreadTimeOut is false, the number of threads will gradually reach saturation and everyone will block and wait.

4. Can non-core threads be core threads?

The thread pool does not distinguish between core threads and non-core threads, but adjusts according to different processing of the current thread pool capacity state, so it looks like there are core threads and non-core threads, but actually meets the concurrency state expected by the thread pool.

5. How does Runnable execute in thread pools?

The e thread executes the Worker, and the Worker keeps getting tasks from the blocking queue to execute.