Concurrent programming - thread pool

Thread pool introduction

How Java builds threads

  • new Thread
  • new Runnable
  • new Callable

In order to avoid unnecessary performance caused by frequent creation and destruction of threads, a thread pool is generally used when using threads.

How to use thread pool:

public static void main(String[] args) {
    
    
    // 线程池的核心线程数如何设置
    // 任务可以分为两种:CPU密集,IO密集。
    ThreadPoolExecutor executor = new ThreadPoolExecutor(
            1,
            2,
            1,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1),
            new ThreadFactory() {
    
    
                @Override
                public Thread newThread(Runnable r) {
    
    
                    Thread t = new Thread(r);
                    // ...
                    return t;
                }
            },
            new ThreadPoolExecutor.AbortPolicy()
    );

    executor.execute(任务);
    executor.submit(有返回结果的任务);
}

Denial policy provided by the thread pool:

  • AbortPolicy: throws an exception.
  • CallerRunsPolicy: Let the thread that submitted the task handle this task.
  • DiscardPolicy: discard tasks
  • DiscardOldestPolicy: Discard the front task of the queue and try to add the current task to it.

Thread pool core properties

// AtomicInteger,就是一个int,写操作用CAS实现,保证了原子性
// ctl维护线程池的2个核心内容:
// 1:线程池状态(高3位,维护着线程池状态)
// 2:工作线程数量(核心线程+非核心线程,低29位,维护着工作线程个数)
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// COUNT_BITS=29
private static final int COUNT_BITS = Integer.SIZE - 3;
// 工作线程的最大个数
// 00100000 00000000 00000000 00000000 - 1
// 000111111111111111111111111111111  
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;


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;

// 拿到线程池状态
private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
// 拿到工作线程个数
private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }

Thread pool status

  1. RUNNING: Normal running state, accepting new tasks and processing tasks in the waiting queue. The initialization state of the thread pool is RUNNING. Once the thread pool is created, it is in the RUNNING state and the number of tasks in the thread pool is 0.
  2. SHUTDOWN: New task submissions will not be accepted, but tasks in the waiting queue will continue to be processed. When the shutdown() method of the thread pool is called, the thread pool changes from RUNNING to SHUTDOWN.
  3. STOP: Do not accept new task submissions, no longer process tasks in the waiting queue, and interrupt the thread executing the task. When the shutdownNow() method of the thread pool is called, the thread pool changes from (RUNNING or SHUTDOWN) to STOP.
  4. TIDYING: All tasks are destroyed, workCount is 0, and when the state of the thread pool is converted to the TIDYING state, the hook method terminated() will be executed.
  5. TERMINATED: The thread pool is completely terminated.

core method

execute method

Through the execute method, you can see the overall execution process of the thread pool processing task.

public void execute(Runnable command) {
    
    
    
    if (command == null)
        throw new NullPointerException();
    // 拿到ctl
    int c = ctl.get();
    // 通过ctl获取当前工作线程个数
    if (workerCountOf(c) < corePoolSize) {
    
    
        // true:代表是核心线程,false:代表是非核心线程
        if (addWorker(command, true))
            // 如果添加核心线程成功,return结束掉
            return;
        // 如果添加失败,重新获取ctl
        c = ctl.get();
    }
    // 核心线程数已经到了最大值、添加时,线程池状态变为SHUTDOWN/STOP
    // 判断线程池是否是运行状态 && 添加任务到工作队列
    if (isRunning(c) && workQueue.offer(command)) {
    
    
        // 再次获取ctl的值
        int recheck = ctl.get();
        // 再次判断线程池状态。  DCL
        // 如果状态不是RUNNING,把任务从工作队列移除。
        if (! isRunning(recheck) && remove(command))
            // 走一波拒绝策略。
            reject(command);
        // 线程池状态是RUNNING。
        // 判断工作线程数是否是0个。
        // 可以将核心线程设置为0,所有工作线程都是非核心线程。
        // 核心线程也可以通过keepAlived超时被销毁,所以如果恰巧核心线程被销毁,也会出现当前效果
        else if (workerCountOf(recheck) == 0)
            // 添加空任务的非核心线程去处理工作队列中的任务
            addWorker(null, false);
    }
    // 可能工作队列中的任务存满了,没添加进去,到这就要添加非核心线程去处理任务
    else if (!addWorker(command, false))
        // 执行拒绝策略!
        reject(command);
}

The main logic of this method is:

  1. If there are currently fewer threads running than corePoolSize, attempts to start a new thread with the given task as its first task. This call checks runState and workerCount atomically to prevent false alarms when threads should not be added.
  2. If the task can be queued successfully, then we still need to check again if a new thread should be added (as the existing thread may have died since the last check), or if the thread pool has been closed since entering this method. So we recheck the status and roll back the queue if necessary and start a new thread if it's stopped.
  3. If we are unable to queue the task then we try to add a new thread. If it fails, we know we're down or saturated, so we reject the task.

addWorker adds a worker thread

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    xxx:
    for (;;) {
    
    
      
        int c = ctl.get();
        int rs = runStateOf(c);
        // 判断线程池状态
        if (rs >= SHUTDOWN &&
              // 判断如果线程池的状态为SHUTDOWN,还要处理工作队列中的任务
              // 如果你添加工作线程的方式,是任务的非核心线程,并且工作队列还有任务
            ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
            return false;

        // 判断工作线程个数
        for (;;) {
    
    
            
            int wc = workerCountOf(c);
            // 判断1:工作线程是否已经 == 工作线程最大个数
            // 判断2-true判断:判断是核心线程么?如果是判断是否超过核心线程个数
            // 判断2-false判断:如果是非核心线程,查看是否超过设置的最大线程数
            if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            // 对工作线程进行 + 1操作
            if (compareAndIncrementWorkerCount(c))
                // +1成功,跳出外层循环,执行添加工作线程的业务
                // 以CAS方式,对ctl+1,多线程并发操作,只有会有一个成功
                break xxx;
            // 重新拿ctl,
            c = ctl.get();
            // 判断线程池状态是否有变化
            if (runStateOf(c) != rs)
                continue xxx;
        }
    }

    // 添加工作线程的业务  
    boolean workerStarted = false;
    boolean workerAdded = false;
    // Worker就是工作线程
    Worker w = null;
    try {
    
    
        // 创建工作线程,将任务传到Worker中
        w = new Worker(firstTask);
        final Thread t = w.thread;
        // 只有你写的线程工厂返回的是null,这里才会为null
        if (t != null) {
    
    
            // 获取锁资源
            final ReentrantLock mainLock = this.mainLock;
            // 加锁。  因为我要在启动这个工作线程时,避免线程池状态发生变化,加锁。
            mainLock.lock();
            try {
    
    
                // 重新获取ctl,拿到线程池状态
                int rs = runStateOf(ctl.get());
                // DCL i think you know~~~
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
    
    
                   	// 判断Worker中的thread是否已经启动了,一般不会启动,除非你在线程工厂把他启动了
                    if (t.isAlive()) 
                        throw new IllegalThreadStateException();
                    // 将工作线程存储到hashSet中
                    workers.add(w);
                    // 获取工作线程个数,判断是否需要修改最大工作线程数记录。
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    // 工作线程添加成功     0
                    workerAdded = true;
                }
            } finally {
    
    
                mainLock.unlock();
            }
            // 如果添加成功
            if (workerAdded) {
    
    
                // 启动工作线程
                t.start();
                // 设置标识为true
                workerStarted = true;
            }
        }
    } finally {
    
    
        // 如果工作线程启动失败
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

// 如果添加工作线程失败,执行
private void addWorkerFailed(Worker w) {
    
    
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        // 说明worker可能存放到了workers的hashSet中。
        if (w != null)
            // 移除!
            workers.remove(w);
        // 减掉workerCount的数值 -1
        decrementWorkerCount();
        // 尝试干掉自己
        tryTerminate();
    } finally {
    
    
        mainLock.unlock();
    }
}

The main logic of this method is:

  1. Check whether it is closing or has been closed. If so, decide whether to return false based on whether there are tasks and whether the queue is empty.
  2. If it is not closed, then check whether the current number of threads exceeds the number of core threads or the maximum number of threads, and returns false if it exceeds.
  3. If the thread number limit is not exceeded, then increase workerCount through CAS operation, and jump out of the loop if successful. If it fails, reread ctl and recalculate runState, and continue looping if runState changes. If no change occurs, continue trying to increase workerCount.
  4. Create a Worker object and start the corresponding thread. Returns true if startup is successful. If the startup fails, call the addWorkerFailed() method to handle the failure.

runWorker executes tasks

final void runWorker(Worker w) {
    
    
    // 拿到当前线程对象
    Thread wt = Thread.currentThread();
    // 拿到worker中存放的Runnable
    Runnable task = w.firstTask;
    // 将worker中的任务清空
    w.firstTask = null;
    boolean completedAbruptly = true;
    try {
    
    
        // 如果Worker自身携带任务,直接执行
        // 如果Worker携带的是null,通过getTask去工作队列获取任务
        while (task != null || (task = getTask()) != null) {
    
    
            w.lock();
            // 判断线程池状态是否大于等于STOP,如果是要中断当前线程
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 // 中断当前线程(DCL)
                 (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);
    }
}

The main logic of this method is:

  1. Get the current thread and task. If the task is not empty, execute the task. If the task is empty, get the task from the task queue. If the acquisition task fails, exit the loop.
  2. Before executing the task, check whether the thread is interrupted, and if interrupted, stop executing the task. If not interrupted, the task is executed. If a runtime exception or error is thrown, the exception is caught and logged. If any other exception is thrown, it is converted to an error and thrown.
  3. After executing the task, set the task to null, increase the number of completed tasks, and unlock the worker.
  4. If the task is empty at the end of the loop, it means that the task queue is closed or empty, set completedAbruptly to false. Otherwise, it means there are still tasks in the task queue, and keep completedAbruptly as true.
  5. Finally, handle the worker exit and decide whether to reset the thread pool state based on the value of completedAbruptly.

getTask worker thread queues up tasks

private Runnable getTask() {
    
    
    boolean timedOut = false; // Did the last poll() time out?

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

        // 线程池状态判断
        // 如果线程池状态为SHUTDOWN && 工作队列为空
        // 如果线程池状态为STOP
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            // 对工作线程个数--
            decrementWorkerCount();
            return null;
        }

        // 对数量的判断。
        int wc = workerCountOf(c);

        // 判断核心线程是否允许超时?
        // 工作线程个数是否大于核心线程数
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // 判断工作线程是否超过了最大线程数 && 工作队列为null
        if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) {
    
    
            // 工作线程数有问题,必须-1,干掉当前工作线程
            // 工作线程是否超过了核心线程,如果超时,就干掉当前线程
            // 对工作线程个数--
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
    
    
            // 如果是非核心,走poll,拉取工作队列任务,
            // 如果是核心线程,走take一直阻塞,拉取工作队列任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
            	// 当工作队列没有任务时,这时就会被Condition通过await阻塞线程
            	// 当有任务添加到工作线程后,这是添加完任务后,就会用过Condition.signal唤醒阻塞的线程
                workQueue.take();
            if (r != null)
                return r;
            // 执行的poll方法,并且在指定时间没拿到任务,
            timedOut = true;
        } catch (InterruptedException retry) {
    
    
            timedOut = false;
        }
    }
}

The main logic of this method is:

  1. Check the thread pool status, if the thread pool has been closed or stopped, and the task queue is empty, reduce the number of workers and return null.
  2. Check if the number of workers exceeds the maximum number of threads or core threads, if so, reduce the number of workers and return null.
  3. If the number of workers does not exceed the maximum number of threads or core threads, the task is obtained from the task queue. If the retrieval task fails, null is returned. If the task is obtained successfully, the task is returned.
  4. If an interruption occurs while fetching the task, retry fetching the task. If the task cannot be obtained within the specified time, the timedOut flag is set to true. If the task cannot be obtained within the specified time and a timeout is allowed, the number of workers is reduced and null is returned.

Guess you like

Origin blog.csdn.net/qq_28314431/article/details/133121175