Chat thread pool ThreadPoolExecutor

Posted by rayk on Thu, 17 Feb 2022 07:55:52 +0100

problem

Let's talk about three questions first and look at the source code with questions:

  1. How does the thread pool keep the core thread from dying?
  2. If the number of core threads is 0, will new tasks be queued first or run directly? Will the process pool eventually die out?
  3. Allow the core thread to timeout. Where is the impact point

Examples

Custom thread pool

class MyThreadPool extends ThreadPoolExecutor {

        public MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        }

        public MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
        }

        public MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
        }

        public MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
        }

        @Override
        protected void beforeExecute(Thread t, Runnable r) {
            System.out.println("Before execution!!");
            super.beforeExecute(t, r);
        }

        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            System.out.println("After execution!!!");
            super.afterExecute(r, t);
        }
    }

Examples

public ThreadPoolExecutor getThreadPool() {
        return new MyThreadPool(0,
                10,
                5,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10000));
    }
public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new Demo().getThreadPool();
        for (int i = 0; i < 1; i++) {
            threadPool.execute(() -> {
                System.out.println("Performing tasks");
            });
        }
    }

result

Source code analysis

Pre knowledge

    /**
     * rs: Running status of thread pool, including RUNNING SHUTDOWN STOP TYDYING TERMINATED
     * wc: The number of running threads in the thread pool
     * private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // ctl Throughout the thread pool, the upper 3 bits are used to save the running state and the lower 29 bits are used to save the number of threads
     * private static final int COUNT_BITS = Integer.SIZE - 3; //32-3
     * private static final int CAPACITY = (1 << COUNT_BITS) - 1; //Shift the binary of 1 by 29 bits to the right, and then subtract 1 to indicate the maximum thread capacity
     *
     * //The running state is saved in the upper 3 bits of the int value (all values are shifted to the left by 29 bits)
     * private static final int RUNNING = -1 << COUNT_BITS;// Receive new tasks and execute the tasks in the queue
     * private static final int SHUTDOWN = 0 << COUNT_BITS;// New tasks are not received, but tasks in the queue are executed
     * private static final int STOP = 1 << COUNT_BITS;// New tasks in the queue, not in execution, not interrupted
     * private static final int TIDYING = 2 << COUNT_BITS; //All tasks have ended, and the number of threads is 0. The thread pool in this state will call the terminated() method
     * private static final int TERMINATED = 3 << COUNT_BITS;// terminated()Method execution complete
     *
     *
     */

entrance

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        // Get thread pool running status
        int c = ctl.get();
        // corePoolSize = 0, this if will not enter
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // Add task to queue
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
            	// Get the task from the queue, use the thread factory to create a new thread and execute the task
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

addWorker

Create a new worker object, including a new thread object

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
        	// Create a new thread task
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                	// Start the thread, corresponding to the Worker's run method
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

runWorker

Run worker method

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        	// Get new tasks from the worker or get tasks from the queue
        	// getTask() supports timeout return, which is determined by allowcorethreadtimeout 𞓜 WC > corepoolsize
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                	// The hook method is executed before executing the task. By default, it is an abstract method. The specification needs to call super while the subclass rewrites this method Beforeexecute method to support level expansion
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                    	// The hook method is executed after the task is executed. By default, it is an abstract method. The specification needs to call super while the subclass rewrites this method Afterexecute method to support level expansion
                        afterExecute(task, thrown);
                    }
                } finally {
                	// After the current task is executed once, the subsequent tasks are taken from the blocking queue
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
        	// while(...) When the conditions are not met, process the thread that is about to end, including judging whether to change the thread pool state, etc
            processWorkerExit(w, completedAbruptly);
        }
    }

getTask

Get the task from the queue and support the timeout mechanism

private Runnable getTask() {
        boolean timedOut = false; 

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Whether to start the timeout mechanism is affected by allowCoreThreadTimeOut or the number of core threads
            // When allowCoreThreadTimeOut = true or corePoolSize = 0 is set, the timeout mechanism is started
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			// If you didn't get the task in the last cycle, this timedOut = true
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
            	// If you don't get the task within a fixed time, set timedOut = true to enter the next cycle. If there is still no task,
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

summary

Back to the first few questions:

How does the thread pool keep the core thread from dying?

Block the core thread by blocking the queue take method (do not open timeout)

If the number of core threads is 0, will new tasks be queued first or run directly? Will the process pool eventually die out?

The core thread is 0. The new task is queued first, and then the thread obtains the task from the queue. Finally, the thread pool will die out, because the method getTask to obtain tasks from the queue supports the timeout mechanism. If there are no tasks in the queue at this time, the thread pool will die out because the core thread is 0 and timed = true.

Allow the core thread to timeout. Where is the impact point?

The main impact points are getTask(), timed is always true, and This method starts the timeout mechanism. If the same thread does not get the task for a long time, the thread will eventually die.

When will exit() of Thread class be called

This method is called by the JVM to release the resources occupied by the thread before the thread dies. The Java definition only provides a JVM call entry, and later allows the JDK to expand this method.

// Give dying threads a chance to clean up the resources they occupy
private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

Topics: Java Back-end thread pool