Java thread pool for Java Concurrent Programming

Keywords: Java Multithreading thread pool JUC

Java thread pool:

  1. Core configuration parameters of thread pool:

    //The timeout of a thread waiting for a task. It takes effect when the number of threads in the thread pool exceeds the corePoolSize. When the thread waiting for a task exceeds keepAliveTime, the thread pool will stop threads that exceed the corePoolSize.
    private volatile long keepAliveTime;
    
    //The default is false, which means that the core thread will not time out waiting for the task; If true, the core thread is allowed to timeout, that is, when there is no task, the core thread will also be stopped by the thread pool.
    private volatile boolean allowCoreThreadTimeOut;
    
    //The minimum number of threads held by the thread pool. When allowCoreThreadTimeOut=true, the number of threads held by the thread pool is 0
    private volatile int corePoolSize;
    
    //The maximum number of threads allowed in the thread pool. When the number of threads reaches the maximumPoolSize, if there are new tasks coming, the thread pool will execute the rejection policy
    private volatile int maximumPoolSize;
    
    //Blocking queue: when the number of threads in the thread pool reaches the corePoolSize, the newly received tasks will be put into the blocking queue, which is divided into bounded queue and unbounded queue; If it is an unbounded queue, the task will always be put into the queue. If it is a bounded queue, when the number of tasks exceeds the queue limit, when corePoolSize < maximumpoolsize, a new thread will be started.
    private final BlockingQueue<Runnable> workQueue;
    
    //The container that holds threads in the thread pool, which records the thread information in the current thread pool
    private final HashSet<Worker> workers = new HashSet<Worker>();
    
    //Thread pool control variable. The lower 29 bits represent the number of thread pools, and the higher 3 bits represent the status of thread pools
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    
  2. Working principle of thread pool: when the thread pool receives a new task, it will judge whether the number of currently started threads exceeds corePoolSize. If not, it will start a new thread to process the request; Otherwise, judge whether the number of tasks in the blocking queue workQueue exceeds the upper bound. If it does not exceed the upper bound, put the task into the blocking queue. Otherwise, judge whether the number of current threads in the thread pool exceeds maximumPoolSize. If it exceeds, execute the rejection policy. Otherwise, start a new thread to execute the task.

  3. Task submission method of thread pool:

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //Judge the number of threads in the current thread pool
        if (workerCountOf(c) < corePoolSize) {
            //Add a new thread processing task
            if (addWorker(command, true))
                return;  
            c = ctl.get();
        }
        //If the thread pool is running, put the task into the blocking queue
        if (isRunning(c) && workQueue.offer(command)) {
            //Re detect the status of the thread pool and the current number of threads to prevent multithreading problems
            int recheck = ctl.get();
            if (!isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //Failed to put into the blocking queue, and failed to add a new processing thread. Execute the reject policy
        else if (!addWorker(command, false))
            reject(command);
    }
    
  4. addWorker method: this method is mainly used to add new threads to the thread pool

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);
            //Check the status of the thread pool. If the thread pool is closed and the queue is empty, return directly
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            //Number of threads in CAS detection thread pool
            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 {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                //Adding a thread to the thread pool requires locking because it is a multi-threaded operation
                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) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
    

    As can be seen from the above source code, when adding threads to the thread pool, they are packaged as workers

  5. Worker of thread pool:

    //The Worker inherits from AQS and takes tasks from the blocking queue. Why do you need to lock them? See the analysis below
    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        //The thread running the worker
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        //Number of tasks executed by thread
        volatile long completedTasks;
        /**
             * Constructor to construct a worker
             */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
    
        //A method executed in a thread. This method is a loop. Internally, it will continuously obtain and execute the threads in the blocking queue
        public void run() {
            runWorker(this);
        }
        
        final void runWorker(Worker w) {
            Thread wt = Thread.currentThread();
            Runnable task = w.firstTask;
            w.firstTask = null;
            w.unlock(); // allow interrupts
            boolean completedAbruptly = true;
            try {
                //Cycle to get the tasks in the blocking queue. When the thread pool is closed, or when the number of threads in the thread pool exceeds maximumPoolSize, or when getting the task timeout, getTask() will return null and exit the cycle
                while (task != null || (task = getTask()) != null) {
                    //The reason for locking here is that idle threads will be interrupted when the thread pool is closed. When the thread pool is interrupted, it will try to obtain the lock. If the lock cannot be obtained, it indicates that the thread is running
                    w.lock();
                    if ((runStateAtLeast(ctl.get(), STOP) ||
                         (Thread.interrupted() &&
                          runStateAtLeast(ctl.get(), STOP))) &&
                        !wt.isInterrupted())
                        wt.interrupt();
                    try {
                        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 {
                            afterExecute(task, thrown);
                        }
                    } finally {
                        task = null;
                        w.completedTasks++;
                        w.unlock();
                    }
                }
                completedAbruptly = false;
            } finally {
                processWorkerExit(w, completedAbruptly);
            }
        }
    }
    
  6. ThreadPoolExecutor provides two methods to close the thread pool:

    1. shutdown(), close the thread pool, and the tasks in the blocking queue will continue to execute, but the thread pool will not receive new tasks at this time.
    2. shutdownNow(), immediately closes the thread pool, does not receive new tasks, and attempts to stop all executing tasks. This method interrupts the executing thread through Thread.interrupt(). Therefore, only the tasks that respond to the interrupt can be stopped through this method, otherwise they can only wait until the task is completed.
    3. It should be noted that neither of the above two methods to stop the thread pool will wait for the thread pool to stop. If you want to wait for the thread pool to stop, you need to call the awaitTermination method after calling the close method. This method will block the current thread for a certain time until the thread pool stops or the current thread times out.

Posted by larsojl on Thu, 14 Oct 2021 14:53:55 -0700