Análisis del código fuente de ThreadPoolExecutor del grupo de subprocesos

Análisis del código fuente de ThreadPoolExecutor del grupo de subprocesos

Descripción general

  1. Cómo se crean los hilos
  2. Parámetros del grupo de subprocesos
  3. Diagrama de flujo de ejecución del grupo de subprocesos
  4. Análisis del código fuente del proceso de ejecución.
  5. Diagrama de estado del grupo de subprocesos
  6. encapsulación de trabajadores
  7. Procesamiento posterior de la ejecución del hilo.
  8. El flujo de trabajo general de las tareas de ejecución de subprocesos en el grupo de subprocesos.

1. Cómo crear hilos

  1. Heredar la clase Thread

Insertar descripción de la imagen aquí

  1. Implementar una interfaz ejecutable

    (Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-UrdyouiC-1691579008075) (C:\Users\hejh\AppData\Roaming\Typora\ imágenes-de-usuario-de-typora\ image-20230809160842904.png)]

  2. Implementar la interfaz invocable y recibir valores de retorno.

    (Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-l497CyhI-1691579008077) (C:\Users\hejh\AppData\Roaming\Typora\ typora-user-images\ image-20230809161233187.png)]

  3. Personalice el grupo de subprocesos o utilice el grupo de subprocesos escrito en el paquete JUC: el método del grupo de subprocesos en realidad no es diferente del anterior, excepto que lo anterior requiere la creación y destrucción frecuentes de subprocesos, lo que provocará un consumo de recursos adicional innecesario. Por lo tanto, en el desarrollo real, el método del grupo de subprocesos definitivamente se usará en Java. Los ejecutores en Java vienen con algunos métodos para crear grupos de subprocesos.

    Bajo la clase Ejecutores:

    [Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-QLJADNcf-1691579008078) (C:\Users\hejh\AppData\Roaming\Typora\ typora-user-images\ image-20230809164946394.png)]

2. Parámetros del grupo de subprocesos

java.util.concurrent.ThreadPoolExecutor es una clase para un grupo de subprocesos personalizado:

 /**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize, //核心线程数
                              int maximumPoolSize, //最大线程数
                              long keepAliveTime, //空闲线程存活时间
                              TimeUnit unit, //时间单位
                              BlockingQueue<Runnable> workQueue, //阻塞队列
                              ThreadFactory threadFactory, //线程工厂
                              RejectedExecutionHandler handler) // 拒绝策略
  • El tamaño del subproceso central del grupo de subprocesos corePoolSize mantendrá un número mínimo de subprocesos en el grupo de subprocesos. Incluso si estos subprocesos manejan el estado inactivo, no se destruirán a menos que se establezca enableCoreThreadTimeOut. El número mínimo de subprocesos aquí es corePoolSize.

  • MaximumPoolSize El número máximo de subprocesos en el grupo de subprocesos. Después de enviar una tarea al grupo de subprocesos, primero descubrirá si hay subprocesos supervivientes inactivos. Si los hay, la tarea se entregará directamente al subproceso inactivo para ejecución. De lo contrario, se almacenará en caché en la cola de trabajo (se presentará más adelante), si la cola de trabajo está llena, se creará un nuevo hilo, luego se eliminará una tarea del encabezado de la cola de trabajo y se entrega al nuevo hilo para su procesamiento y la tarea recién enviada se colocará al final de la cola de trabajo. El grupo de subprocesos no creará nuevos subprocesos sin límite. Tendrá un límite en el número máximo de subprocesos, que se especifica en MaximumPoolSize.

  • keepAliveTime tiempo de supervivencia del subproceso inactivo. Si un subproceso está inactivo y el número actual de subprocesos es mayor que corePoolSize, entonces el subproceso inactivo se destruirá después del tiempo especificado. El tiempo especificado aquí lo establece keepAliveTime.

  • unidad unidad de medida de tiempo de supervivencia del subproceso inactivo unidad keepAliveTime

  • Después de enviar una nueva tarea en la cola de trabajos workQueue , primero ingresará a la cola de trabajos y luego la tarea se sacará de la cola durante la programación de tareas. Hay cuatro colas de trabajo proporcionadas en jdk:

    1. ArrayBlockingQueue es una cola de bloqueo delimitada basada en matrices, ordenada por FIFO. Después de que llegue una nueva tarea, se colocará al final de la cola. La matriz limitada puede evitar problemas de agotamiento de recursos. Cuando la cantidad de subprocesos en el grupo de subprocesos alcanza corePoolSize y llega una nueva tarea, la tarea se colocará al final de la cola, esperando ser programada. Si la cola ya está llena, se crea un nuevo hilo. Si el número de hilos ha alcanzado maxPoolSize, se ejecutará la política de rechazo.

    2. LinkedBlockingQueue es una cola de bloqueo ilimitada basada en una lista vinculada (de hecho, la capacidad máxima es Interger.MAX), ordenada según FIFO. Debido a la naturaleza aproximadamente ilimitada de la cola, cuando el número de subprocesos en el grupo de subprocesos alcanza corePoolSize, nuevas tareas entrarán y se almacenarán en la cola sin crear nuevos subprocesos hasta maxPoolSize. Por lo tanto, cuando se utiliza esta cola de trabajo, el parámetro maxPoolSize En realidad no funciona.

    3. SynchronousQueue es una cola de bloqueo que no almacena en caché las tareas, el productor coloca una tarea y debe esperar hasta que el consumidor la elimine. Es decir, cuando llega una nueva tarea, no se almacenará en caché, sino que se programará directamente para ejecutar la tarea. Si no hay subprocesos disponibles, se creará un nuevo subproceso. Si el número de subprocesos alcanza maxPoolSize, Se ejecutará una política de rechazo. 4. PriorityBlockingQueue es una cola de bloqueo ilimitada con prioridad, que se implementa a través del parámetro Comparador.

  • La fábrica de subprocesos threadFactory se utiliza al crear un nuevo subproceso y se puede utilizar para establecer el nombre del subproceso, ya sea un subproceso demonio, etc.

  • política de rechazo del controlador Cuando las tareas en la cola de trabajos han alcanzado el límite máximo y el número de subprocesos en el grupo de subprocesos también ha alcanzado el límite máximo, cómo manejar si se envía una nueva tarea. La estrategia de rechazo aquí es para resolver este problema. JDK proporciona 4 estrategias de rechazo:

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-rJeEfDbi-1691579008079) (C:\Users\hejh\AppData\Roaming\Typora\ typora-user-images\ image-20230809165906985.png)]

3. Diagrama de flujo de ejecución del grupo de subprocesos

4. Análisis del código fuente del proceso de ejecución.

PD: Los siguientes métodos están todos bajo la clase java.util.concurrent.ThreadPoolExecutor

1. Propiedades

    //原子类,用于保存线程池的状态和工作线程的数量。总共32位,前3位表示线程池的状态,后29位表示工作线程的数量 
    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;

    // 线程池的状态,保存在前三位
    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;

    // ctl的构造和 拆分其得到线程池的状态和工作线程的数量
    private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
    private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }

2. ejecutar método

public void execute(Runnable command) {
    
    
        //健壮性判断
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        //如果工作线程的数量小于核心线程数,则创建线程处理任务,addWorker之前都要检查线程池状态  
        if (workerCountOf(c) < corePoolSize) {
    
    
            if (addWorker(command, true))
                return;
            //防止多线程情况下c变成其他值
            c = ctl.get();
        }
        //核心线程已满,如果线程池处于运行状态,将任务放进阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
    
    
            int recheck = ctl.get();
            //重复检查,如果不是运行状态,移除队列中的任务,拒绝任务。addWorker之前都要检查线程池状态
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //如果工作线程为0,就添加一个线程,避免出现队列任务没有线程执行的情况。
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
       //添加线程处理这个任务,如果失败则拒绝。
        else if (!addWorker(command, false))
            reject(command);
    }

3. método addWorker

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

            // 仅在必要时检查队列是否为空。
            if (rs >= SHUTDOWN && // 线程池属于关闭状态,无法接受新任务,也无法处理池中任务,
                ! (rs == SHUTDOWN && // !SHUTDOWN,即是STOP,TIDYING,TERMINATED
                   firstTask == null && // firstTask不为空 -> 这里对应上述的addWorker(null,false)
                   ! workQueue.isEmpty()))// 工作队列为空
                return false;

            for (;;) {
    
    
                int wc = workerCountOf(c);
                //工作线程数再判断
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //通过CAS操作增加工作线程数
                if (compareAndIncrementWorkerCount(c))
                    break retry; 
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // CAS 由于workerCount 变化而失败;重试内循环
            }
        }
        //开始添加工作线程
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
    
    
            w = new Worker(firstTask);
            final Thread t = w.thread;
            //判断t!=null的目的,防止t创建失败
            if (t != null) {
    
    
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();//加锁的原因就是largestPoolSize,
                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)) {
    
    //处于shutdown状态,firstTask == null这里对应上述的addWorker(null,false)
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);//workers:线程池中所有工作线程的集合,
                        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;
    }

4. Método addWorkerFailed, agrega el método de falla del subproceso

//通过上面的代码,总结哪些情况会出现添加失败的情况:
1,线程池不在运行状态
2,线程已经启动    
这2种情况都没有添加成功    workers.remove(w);不会有问题。
private void addWorkerFailed(Worker w) {
    
    
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();//因为decrementWorkerCount而加锁
        try {
    
    
            if (w != null)
                workers.remove(w);
            decrementWorkerCount();
            //重新检查线程池终止情况,以防此线程存在影响线程池终止。
            tryTerminate();
        } finally {
    
    
            mainLock.unlock();
        }
    }

5. Diagrama de estado del grupo de subprocesos

[Error en la transferencia de la imagen del enlace externo. El sitio de origen puede tener un mecanismo anti-leeching. Se recomienda guardar la imagen y cargarla directamente (img-SnaGuPtK-1691579008080) (C:\Users\hejh\AppData\Roaming\Typora\ imágenes-de-usuario-de-typora\ image-20230809180334809.png)]

6. Embalaje para trabajadores

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
    {
    
    
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        Worker(Runnable firstTask) {
    
    
            setState(-1); // 添加标识,worker运行前,禁止中断(AQS)
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** 具体运行调用外部(threadPoolExecutor)的方法  */
        public void run() {
    
    
            runWorker(this);
        }

        // Lock methods AQS的状态
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
    
    
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
    
    
            if (compareAndSetState(0, 1)) {
    
    
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
    
    
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        {
    
     acquire(1); }
        public boolean tryLock()  {
    
     return tryAcquire(1); }
        public void unlock()      {
    
     release(1); }
        public boolean isLocked() {
    
     return isHeldExclusively(); }

        void interruptIfStarted() {
    
    
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
    
    
                try {
    
    
                    t.interrupt();
                } catch (SecurityException ignore) {
    
    
                }
            }
        }
    }

método java.util.concurrent.ThreadPoolExecutor#runWorker

final void runWorker(Worker w) {
    
    
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
    
    
            //任务不为null,就一直循环,否则调用getTask尝试从阻塞队列获取任务
            while (task != null || (task = getTask()) != null) {
    
    
                w.lock();// 加锁的目的是表示当前任务正在执行,你shutdown任务也不会中断
                // 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 {
    
    
                    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);
        }
    }

método getTask:

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

        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);
            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //
            if ((wc > maximumPoolSize || (timed && timedOut))//当前工作线程数大于最大线程数 ,后面判断表示是否是允许核心线程超时并且真的超时
                && (wc > 1 || workQueue.isEmpty())) {
    
    //工作线程 > 1或者 阻塞队列为空
                if (compareAndDecrementWorkerCount(c))// 干掉当前工作线程并返回null,CAS的方式,如果失败,重新走一遍
                    return null;
                continue;
            }

            try {
    
    
                Runnable r = timed ?
                    // 这里是可能出现超时情况并且允许回收线程,那就阻塞这么久拿阻塞队列的任务
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                //这里是线程阻塞在这,等待任务,不参与回收的情况,直到触发signal方法被唤醒,走catch继续下次循环
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
    
    
                timedOut = false;
            }
        }
    }

7. Procesamiento posterior de la ejecución del hilo.

Método ProcessWorkerExit:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
        if (completedAbruptly) // 如果停止,减一个工作线程数
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();//加锁为了移除工作线程workers.remove(w);
        try {
    
    
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
    
    
            mainLock.unlock();
        }

        tryTerminate();// 尝试干掉线程池

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
    
    
            // 如果不是认为停止,需要判断线程是否需要追加一个线程处理任务
            if (!completedAbruptly) {
    
    
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;// 查看核心线程是否允许超时
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;// 如果允许超时,并且工作队列不是空,就将min设置为1
                if (workerCountOf(c) >= min)// 如果工作线程数量大于核心线程数,就直接结束
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

8. El flujo de trabajo general de las tareas de ejecución de subprocesos en el grupo de subprocesos.

Insertar descripción de la imagen aquí

Descripción aproximada del proceso:

  1. Al ejecutar una tarea, si el número de subprocesos principales no alcanza el valor corePoolSize, se creará un subproceso. De lo contrario, se coloca en la cola de tareas.
  2. Cuando la cola de tareas está llena, se crean subprocesos, pero el número total de subprocesos no puede exceder el valor máximo de PoolSize.
  3. Si la cola de tareas está llena y el número de subprocesos alcanza el valor máximo de PoolSize, se ejecutará la política de falla.
  4. El subproceso de trabajo sondea continuamente las tareas de sondeo en la cola. Si la encuesta está vacía, la ejecución del subproceso de trabajo finaliza (subproceso de reciclaje).
  5. Si el número de subprocesos de trabajo <= el número de subprocesos principales corePoolSize, utilice take para obtener tareas de la cola (los subprocesos principales siempre esperan).

En primer lugar, getTask está en un bucle for infinito. Se considera que el número de subprocesos actualmente en ejecución <= el número de subprocesos principales, y se llama directamente al método take de la cola de bloqueo, que se bloquea indefinidamente hasta que se ingresan los datos. la cola y se despierta para salir del bucle for.

Si el número de subprocesos actualmente en ejecución > el número de subprocesos principales, si no hay datos en la cola, bloqueará keepAliveTime antes de regresar. Al continuar el bucle for, si la cola está vacía, saldrá y destruirá el subproceso. bloqueado en la cola.

Resumen del proceso del grupo de subprocesos

Cuando se envía la tarea, si la cantidad de subprocesos actualmente en ejecución es menor que la cantidad de subprocesos principales, construya un trabajador y llame al inicio del trabajador para iniciar un subproceso. El método de ejecución del hilo es un bucle while. En el bucle, se llama a getTask para recuperar datos de la cola de bloqueo global. Los datos se ejecutan inmediatamente después de la recuperación y luego se bloquean en la cola del método getTask.

Cuando se envía la tarea, si el número de subprocesos actualmente en ejecución es mayor o igual que el número de subprocesos principales y la cola no está llena, se coloca en la cola de bloqueo global. En este momento, el subproceso bloqueado por getTask arriba se despertará para ejecutar la tarea comercial.

Cuando se envía la tarea, si la cantidad de subprocesos actualmente en ejecución es mayor o igual que la cantidad de subprocesos principales y la cola está llena, continuará determinando si la cantidad de subprocesos actualmente en ejecución excede la cantidad máxima de subprocesos. excede el número máximo de subprocesos, se utilizará la política de rechazo de tareas; de lo contrario, se construye una tarea trabajador, llame al inicio del trabajador para iniciar un subproceso y realice el primer paso de la operación getTask del ciclo while.

operación getTask

Cuando getTask, si el número de subprocesos actualmente en ejecución está dentro del número de subprocesos principales y los subprocesos no están en tensión, se bloquearán en la cola indefinidamente hasta que los datos se coloquen en la cola y luego vuelvan a la ejecución. Los subprocesos actualmente en ejecución son mayores que el número de subprocesos principales, el subproceso comenzará Hay más. KeepAliveTime se bloqueará. Si no provienen datos de la cola durante este período, el hilo de trabajo se destruirá y se permitirá reciclarlo.

¿Cuál es el papel de KeepAliveTime?

keepAliveTime (tiempo de retención de actividad del subproceso): el tiempo que el subproceso de trabajo del grupo de subprocesos permanece vivo después de estar inactivo. Este parámetro solo es útil cuando el número de subprocesos es mayor que corePoolSize. Los subprocesos inactivos que excedan este tiempo serán terminados; pero si enableCoreThreadTimeOut se establece en verdadero, el recuento de subprocesos centrales también se destruirá si no se ejecuta la tarea hasta que keepAliveTime tiempo. Si hay muchas tareas y el tiempo de ejecución de cada tarea es relativamente corto, puede aumentar este tiempo para mejorar la utilización del subproceso.

Supongo que te gusta

Origin blog.csdn.net/Edward_hjh/article/details/132194867
Recomendado
Clasificación