Thread Pool for Java Foundation

Posted by lobski on Sun, 05 Sep 2021 18:17:33 +0200

Thread Pool for Java Foundation

  • What is the main workflow for a thread pool?

Core code: ThreadPoolExecutor class

public void execute(Runnable command) {
    //If the task is null, throw a null pointer exception
    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, by returning false.
         *
         * 2. 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.
         *
         * 3. 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.
         */
    //Gets the combined value of the current thread pool state + number of threads variable
    int c = ctl.get();
    //1. If the current number of valid threads is less than the number of core threads, call addWoker to execute the task (that is, create a thread to execute the task)
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //2. If the current number of valid threads > the number of core threads, and the current thread pool state is running, try a non-blocking method to put tasks in the task queue (failure returns false)
    if (isRunning(c) && workQueue.offer(command)) {
        //Second check
        int recheck = ctl.get();
        //If the current thread pool state is not Running, delete the task from the queue and execute the rejection policy
        if (! isRunning(recheck) && remove(command))
            //Invoke Rejection Policy
            reject(command);
        //If the current thread pool is empty, add a thread
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //3. If the blocking queue is full, call addWorker to execute the task (that is, create a thread to execute the task)
    else if (!addWorker(command, false))
        //If the thread creation fails, the thread rejection policy is invoked
        reject(command);
}
  • What are the workqueues for a thread pool?How do you understand bounded queues and bounded queues?

    Three queues:

    1. ArrayBlockingQueue: Bounded queue (array-based)
    2. LinkedBlockingQueue: Has/Unbounded Queues (based on chained lists, parameters are bounded, unbound without passing [default Integer.MAX_VALUE])
    3. SynchronousQueue: Synchronous queue (blocked queue that does not store elements)

    Bounded Queue:

    There is a queue of fixed size.For example, a LinkedBlockingQueue with a fixed size of 0 is set for SynchronousQueue to be reused by producers and consumers.

    Unbounded Queue:

    No queue of fixed size was set.Can be directly listed until overflow, defaulting to Integer.MAX_VALUE(2147483647).

  • What are the rejection policies for thread pools and what are them?Can I customize the rejection policy?

    Rejection policies protect thread pools against flow limitations.

    There are four rejection strategies:

    1.ThreadPoolExecutor.AbortPolicy: Discard task and throw RejectedExecutionException exception

    2.ThreadPoolExecutor.DiscardPolicy: Discard the task without throwing an exception

    3.ThreadPoolExecutor.DiscardOldestPolicy: Discard the top task in the queue and try to execute it again (repeat the process)

    4.ThreadPoolExecutor.CallerRunsPolicy: This task is handled by the calling thread

    (πŸ˜ƒThe RejectedExecutionHandler interface can be implemented based on practical application scenarios, and rejection policies such as logging or tasks that cannot be handled by persistent storage can be customized to facilitate problem location and analysis.

    public class Test {
    
        public static class MyTask implements Runnable {
            @Override
            public void run() {
                System.out.println("thread id:" + Thread.currentThread().getId());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args){
            LinkedBlockingDeque queue = new LinkedBlockingDeque(4);
            ExecutorService es = new ThreadPoolExecutor(
                    5,
                    5,
                    0L,
                    TimeUnit.MILLISECONDS,
                    queue,
                    Executors.defaultThreadFactory(),
                    new RejectedExecutionHandler() {
                        @Override
                        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                            System.out.println("Here is the custom thread rejection policy");
                        }
                    }
            );
    
            MyTask task = new MyTask();
            for (int i=0; i<100; i++) {
                es.submit(task);
            }
        }
    }
    
    Result:
    thread id:12
    thread id:16
     Here is the custom thread rejection policy
    
  • How do I create and stop a thread pool?Why is it not recommended to use Executors to build thread pools?

    1 Thread pool creation:

    1. Created with ThreadPoolExecutor, various parameters of the thread pool are passed

    2. Create with Executors and call different thread pool types (such as newFixedThreadPool)

    (2) Thread pool termination:

    1. Elegant Exit-shutdown()

    If the shutdown() method is called, the thread pool is in the SHUTDOWN state, and the thread pool does not accept new tasks, it waits for all tasks to complete.

    2. Forced Exit-shutdownNow()

    If the shutdownNow() method is called, the thread pool is in the STOP state, and the thread pool does not accept new tasks and attempts to terminate the task being executed.

    (3) Why not recommend using Executors to build thread pools

    Because many of the methods offered by Executors default to an unbound LinkedBlockingQueue, bounded queues can easily cause OOM (memory overflow) under high load, it is strongly recommended that bounded queues be used.

  • What kinds of thread pools are there and what are their usage scenarios?

    newSingleThreadPool: A single-threaded thread pool that can be used in scenarios where sequential execution is guaranteed and only one thread is executing

    (2) newFixedThreadPool: A fixed-size thread pool that limits the number of threads given concurrent pressure

    (3) newCachedThreadPool: A cacheable thread pool that is better suited for tasks with less execution time

    (4) newScheduled ThreadPool: for scenarios where tasks are performed regularly and periodically

    The thread pool provided by newWorkStealingThreadPool:jdk1.8, implemented at the bottom using ForkJoinPool, is suitable for scenarios where large tasks are decomposed and executed in parallel.The Fork/Join framework uses the work-stealing algorithm, which divides tasks into several independent subtasks.

  • What are the states of the thread pool, what are the design mechanisms for the states, and how do the States switch from one another?

    1 State

    1. RUNNING: Accept new tasks and handle tasks in the blocked queue

    2. SHUTDOWN: Deny new tasks but handle tasks that are blocking the queue

    3. STOP: Rejects new tasks and discards tasks blocking the queue, interrupting ongoing processing tasks

    4. TIDYING: All tasks are executed (including those in the blocking queue), the current thread pool active thread is 0, and the terminated method will be called

    5. TERMINATED: Terminate state, the state after the terminated method call is completed

      /* The runState provides the main lifecycle control, taking on values:
      *
      *   RUNNING:  Accept new tasks and process queued tasks
      *   SHUTDOWN: Don't accept new tasks, but process queued tasks
      *   STOP:     Don't accept new tasks, don't process queued tasks,
      *             and interrupt in-progress tasks
      *   TIDYING:  All tasks have terminated, workerCount is zero,
      *             the thread transitioning to state TIDYING
      *             will run the terminated() hook method
      *   TERMINATED: terminated() has completed
      /
      

    (2) State Design Mechanism

    Use an AtomicInteger type variable ctl to encapsulate the state of the thread pool and the number of threads currently active

    The ctl is a total of 32 bits, with the top 3 bits representing the state of the thread pool:

    ​ 111: RUNNING

    ​ 000: SHUTDOWN

    ​ 001: STOP

    ​ 010: TIDYING

    ​ 011: TERMINATED

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    
    // runState is stored in the high-order bits
    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;
    

    3. How the state is switched

 /* RUNNING -> SHUTDOWN
     *    On invocation of shutdown(), perhaps implicitly in finalize()
     * (RUNNING or SHUTDOWN) -> STOP
     *    On invocation of shutdownNow()
     * SHUTDOWN -> TIDYING
     *    When both queue and pool are empty
     * STOP -> TIDYING
     *    When pool is empty
     * TIDYING -> TERMINATED
     *    When the terminated() hook method has completed
     /
  1. RUNNING -> SHUTDOWN: An explicit call to the shutdown() method, or an implicit call to finalize(), in which the shutdown() method is called

  2. RUNNING or SHUTDOWN -> STOP: Explicit call to shutdownNow() method

  3. SHUTDOWN -> TIDYING: When both the thread pool and task queue are empty

  4. STOP -> TIDYING: When the thread pool is empty

  5. TIDYING -> TERMINATED: When the terminated() method has finished executing

  • Scenarios for using thread pools, why can thread pools improve performance?

    (1) Use scenarios

    Suitable for high concurrency, batch processing, performance tuning scenarios

    (2) Why improve performance

    Save time on thread creation and destruction

  • What are the important parameters for a thread pool and how do you set them?

    1. Important parameters

    1. CorePoolSize: Number of core threads in the thread pool that will have corePoolSize threads waiting for tasks even if there are no tasks in the thread pool
    2. MaximumPoolSize: The maximum number of threads in the thread pool, regardless of how many tasks there are, the maximum number of worker threads in the thread pool is maximumPoolSize
    3. KeepAliveTime: The length of time a thread survives. When the number of threads in the thread pool is greater than corePoolSize, the thread exits if no task is executable after waiting for keepAliveTime
    4. Unit: The unit used to specify keepAliveTime
    5. workQueue: Queue where submitted tasks will be placed
    6. threadFactory: A thread factory used to create threads, primarily to name threads
    7. handler: deny policy, called when the thread in the thread pool is exhausted and the queue is full

    (2) How to set these parameters

    1. Refer to Executors class settings, test

    2. CPU-intensive: Computing-intensive, most of the time used for CPU operations such as computing logic judgment, minimizing CPU context switching, Number of core threads = Ncpu + 1 (Number of N cores)

    3. IO-intensive: Tasks require a large number of IO operations involving network, disk IO operations, less CPU consumption, number of core threads = 2Ncpu

  • How does the thread pool get the results returned by execution?

    If the result is obtained synchronously, the entry method is execute(Runnable command)

    (2) If the result is obtained asynchronously: the submit method of the ThreadPoolExecutor class

  • Can to create new native threads exception occur, how can I analyze and resolve it?

    Reason:

    The number of threads created exceeds the operating system limit

    Analysis:

    Is the number of threads set reasonable, multiple scheduled tasks exist, and the number of threads is too large

    Solve:

    Set a reasonable number of threads, plus machines, etc.

Topics: Java thread pool