Interview prerequisite: Java thread pool parsing

Keywords: Java css Interview

preface

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

github address

github.com/whx123/Java...

Classic interview questions

  • Interview question 1: the thread pool of Java. What are the functions of various parameters?
  • Interview question 2: according to the internal mechanism of thread pool, what exceptions should be considered when submitting a new task.
  • Interview question 3: what kinds of work queues do thread pools have?
  • Interview question 4: will thread pools using unbounded queues cause memory to soar?
  • Interview question 5: what are some common thread pools and usage scenarios?

Thread pool concept

Thread pool: simply understood, it is a pool for managing threads.

  • It helps us manage threads and avoid increasing the resource consumption of creating and destroying threads. Because a thread is actually an object, creating an object requires a class loading process, destroying an object, and going through the GC garbage collection process, all of which require resource overhead.
  • Improve response speed. If the task arrives, it will be much slower than taking the thread from the thread pool and re creating a thread for execution.
  • reuse. Threads are used up and put back into the pool, which can achieve the effect of reuse and save resources.

Creation of thread pool

Thread pool can be created through 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) 
Copy code

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 lifetime of non core threads in the thread pool
  • Unit: thread idle survival time unit
  • workQueue: the blocking queue for storing tasks
  • threadFactory: 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 policy events in line cities.

Task execution

Thread pool execution process, i.e. corresponding execute() method:

  • When a task is submitted and the number of surviving core threads 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 the corePoolSize, a newly submitted task will be put into the task queue and queued for execution.
  • When the number of surviving threads in the thread pool is equal to the corePoolSize and the workQueue of the task queue is full, judge whether the number of threads reaches the maximum poolsize, 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 maximumPoolSize and there are new tasks coming, the rejection policy is directly adopted.

Four rejection strategies

  • Abortpolicy (throw an exception, default)
  • Discardpolicy (discard task directly)
  • Discard oldestpolicy (discard the oldest task in the queue and continue to submit the current task to the thread pool)
  • CallerRunsPolicy (give it to the thread where the thread pool call is located for processing)

To vividly describe thread pool execution, I use an analogy:

  • Core threads are compared to regular employees of the company
  • Non core threads are compared to outsourced employees
  • Blocking queues are compared to demand pools
  • Submitting a task is like raising a demand
  • When the product raises a demand, the regular employee (core thread) first receives the demand (performs the task)
  • If all regular employees have requirements (i.e. the number of core threads is full), the product will put the requirements into the requirements pool (blocking queue) first.
  • What if the demand pool (blocking queue) is also full, but the product continues to demand at this time? Then please outsource (non core thread) to do it.
  • If all employees (the maximum number of threads is also full) have requirements to do, execute the reject policy.
  • If the outsourcing employee completes the requirements, it will leave the company after a period of idle time.

OK, here we go. Interview question1 - > java thread pool. How do the parameters work? Whether it has been solved. I think it's almost the same to answer this question: the core poolsize, maximumpoolsize and other parameters of the thread pool constructor, and can clearly describe the execution process of the thread pool.

Thread pool exception handling

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

How to handle exceptions when submitting a new task?

Let's start with a 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());
            });
        }

Copy code

Obviously, there will be exceptions in this code. Let's take a look at the execution results

Although there is no result output, no exception is thrown, so we can't perceive the exception in the task, so we need to add try/catch. As shown below:

OK, thread exception handling, We can try...catch directly.

Execution flow of thread pool exec.submit(runnable)

By debugging the submit method with exceptions (it is recommended that you also go to debug. The interior of each method in the figure is where I interrupt), the main execution flow chart of handling the submit method with exceptions is as follows:

  //Constructing feature objects
  /**
     * @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);
    }
    //Thread pool execution
     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);
    }
    //Catch exception
    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);
        }
Copy code

Through the above analysis, the task executed by submit can receive and process the thrown exception through the get method of the Future object. Let's use a demo to see the posture of the get method of the Future object to handle exceptions, as shown in the following figure:

The other two schemes deal with thread pool exceptions

In addition to the above 1. Catching exceptions in the task code try/catch, 2. Receiving thrown exceptions through the get method of the Future object and processing them, there are also the above two schemes:

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

Let's look directly at the correct posture:

ExecutorService threadPool = Executors.newFixedThreadPool(1, r -> {
            Thread t = new Thread(r);
            t.setUncaughtExceptionHandler(
                    (t1, e) -> {
                        System.out.println(t1.getName() + "Exception thrown by thread"+e);
                    });
            return t;
           });
        threadPool.execute(()->{
            Object object = null;
            System.out.print("result## " + object.toString());
        });
Copy code

Operation results:

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

This is a demo of the jdk document:

class ExtendedExecutor extends ThreadPoolExecutor {
    // This is the example given in the jdk document..
    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);
    }
}}
Copy code

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

.

Work queue for thread pool

What kinds of work queues do thread pools have?

  • ArrayBlockingQueue
  • LinkedBlockingQueue
  • DelayQueue
  • PriorityBlockingQueue
  • SynchronousQueue

ArrayBlockingQueue

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

LinkedBlockingQueue

LinkedBlockingQueue (settable capacity queue) is a blocking queue based on the linked list structure. Tasks are sorted by FIFO. The capacity can be set optionally. If it is not set, it will be an unbounded blocking queue with a maximum length of Integer.MAX_VALUE, the throughput is usually higher than arrayblockingqueue; The newFixedThreadPool thread pool uses this queue

DelayQueue

DelayQueue is a queue that delays the execution of a task in a scheduled cycle. Sort according to the specified execution time from small to large, otherwise sort according to the sequence inserted into the queue. The newScheduledThreadPool thread pool uses this queue.

PriorityBlockingQueue

Priority blocking queue is an unbounded blocking queue with priority;

SynchronousQueue

Synchronous queue is a blocking queue that does not store elements. Each insert operation must wait until another thread calls the remove operation. Otherwise, the insert operation is always blocked, and the throughput is usually higher than linkedblockingqueue. This queue is used by the newCachedThreadPool thread pool.

For the interview question: what kinds of work queues do thread pools have? I think it's OK to answer the above ArrayBlockingQueue, LinkedBlockingQueue and synchronuequeue, tell their characteristics, and explain them in combination with the common thread pools used in the corresponding queue (for example, the newFixedThreadPool thread pool uses LinkedBlockingQueue).

Several common thread pools

  • newFixedThreadPool (thread pool with a fixed number of threads)
  • Newcachedthreadpool (thread pool for cacheable threads)
  • Newsinglethreadexecution (single threaded thread pool)
  • Newscheduledthreadpool (thread pool for scheduled and periodic execution)

newFixedThreadPool

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

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, that is, keepAliveTime is 0
  • The blocking queue is an unbounded queue LinkedBlockingQueue

Working mechanism:

  • Submit task
  • If the number of threads is less than the core thread, create the core thread to execute the task
  • If the number of threads is equal to the core thread, add the task to the LinkedBlockingQueue blocking queue
  • If the thread finishes executing the task, it blocks the queue to get the task and continues to execute.

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
                            }
            });
Copy code

IDE specifies JVM parameters: - Xmx8m -Xms8m:

run the above code and throw OOM:

Therefore, the interview question: will the thread pool using unbounded queues lead to a surge in memory?

Answer: Yes, the newFixedThreadPool uses the unbounded blocking queue LinkedBlockingQueue. If the thread takes a long time to execute a task (for example, the demo above is set to 10 seconds), it will lead to more and more tasks in the queue, leading to the soaring use of machine memory, and eventually lead to OOM.

Usage scenario

FixedThreadPool is suitable for processing CPU intensive tasks. It ensures that when the CPU is used by working threads for a long time, it can allocate as few threads 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);
    }
Copy code

Thread pool features:

  • The number of core threads is 0
  • The maximum number of threads is Integer.MAX_VALUE
  • The blocking queue is synchronous queue
  • The idle lifetime of non core threads is 60 seconds

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

Working mechanism

  • Submit task
  • Because there is no core thread, the task is added directly to the SynchronousQueue queue.
  • Judge whether there is an idle thread. If so, take out the task for execution.
  • If there is no idle thread, create a new thread to execute.
  • The thread executing the task can also survive for 60 seconds. If it receives the task during this period, it can continue to live; Otherwise, it will be destroyed.

Example code

  ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName()+"Executing");
            });
        }
Copy code

Operation results:

Usage scenario

It is used to perform 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));
    }
Copy code

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 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 current only thread fetches tasks from the queue, executes one, and then continues fetching. One person (one thread) works day and night.

Example code

  ExecutorService executor = Executors.newSingleThreadExecutor();
                for (int i = 0; i < 5; i++) {
                    executor.execute(() -> {
                        System.out.println(Thread.currentThread().getName()+"Executing");
                    });
        }
Copy code

Operation results:

Usage scenario

It is applicable to the scenario of serial execution of tasks, one task at a time.

newScheduledThreadPool

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
Copy code

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 delay

Working mechanism

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

Example code

    /**
    Create an intermittent task with a given initial delay. The next execution time after that is the time required from the execution to the end of the previous task + * the given interval time
    */
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleWithFixedDelay(()->{
            System.out.println("current Time" + System.currentTimeMillis());
            System.out.println(Thread.currentThread().getName()+"Executing");
        }, 1, 3, TimeUnit.SECONDS);
Copy code

Operation results:

    /**
    Create an intermittent task with a given initial delay, and the execution time of each subsequent task is initial delay + n * delay 
    */
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
            scheduledExecutorService.scheduleAtFixedRate(()->{
            System.out.println("current Time" + System.currentTimeMillis());
            System.out.println(Thread.currentThread().getName()+"Executing");
        }, 1, 3, TimeUnit.SECONDS);;
Copy code

Usage scenario

In the scenario of periodically executing tasks, the number of threads needs to be limited

Back to the interview question: how many common thread pools and usage scenarios?

Answer these four classic thread pools: newFixedThreadPool, newsinglethreadexecution, newCachedThreadPool, newScheduledThreadPool, thread pool characteristics, working mechanism, usage scenarios, and then analyze possible problems, such as memory surge in newFixedThreadPool

Thread pool status

Thread pools have these states: RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED.

   //Thread pool status
   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;
Copy code

Diagram of state switching of thread pool:

RUNNING

  • The thread pool in this state will receive new tasks and process the tasks in the blocking queue;
  • Call the shutdown() method of the thread pool to switch to the SHUTDOWN state;
  • Call the shutdown now () 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 the 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, nor will they 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 status indicates that all tasks have been terminated, and the number of recorded tasks is 0.
  • The terminated() is executed and enters the TERMINATED state

TERMINATED

  • This status indicates that the thread pool has completely terminated

Reference and thanks

Official account number

Posted by sufian on Sat, 20 Nov 2021 01:44:27 -0800