Principio de ejecución del grupo de subprocesos

Tabla de contenido

Introducción básica al grupo de subprocesos

Parámetros del grupo de subprocesos

Estado del grupo de subprocesos y métodos básicos

El proceso de ejecución del grupo de subprocesos

Grupo de hilos incorporado

newFixedThreadPool

newSingleThreadExecutor

newCachedThreadPool

newScheduledThreadPool


Introducción básica al grupo de subprocesos

La tasa de aparición del grupo de subprocesos en el proyecto sigue siendo relativamente alta. Si el grupo de subprocesos se introduce en un idioma más vernáculo, es una colección de subprocesos + cola de tareas. Cuando una solicitud de tarea llega al grupo de subprocesos, se rellena en la cola de tareas, y luego Un escenario simple donde los hilos toman tareas de la cola en un bucle sin fin. Entonces, ¿por qué necesitamos un grupo de subprocesos?

  • Evite la creación y destrucción frecuentes de subprocesos y reduzca el consumo de recursos
  • El grupo de subprocesos puede administrar mejor los recursos de subprocesos, como la asignación, el ajuste y la supervisión
  • Mejora la velocidad de respuesta, cuando llega la tarea, puede responder sin esperar a que se cree el hilo.

Parámetros del grupo de subprocesos

De hecho, jdk nos proporciona varios grupos de subprocesos integrados, pero no se recomienda usarlos. Es mejor crearlos manualmente. Los parámetros del método para crear grupos de subprocesos son los siguientes:

public ThreadPoolExecutor(int corePoolSize, // 核心线程数量
                              int maximumPoolSize, // 线程池最大线程数量
                              long keepAliveTime, // 非核心线程数存活时间
                              TimeUnit unit, // 存活时间单位
                              BlockingQueue<Runnable> workQueue, // 队列,存放任务
                              ThreadFactory threadFactory, // 线程工厂,一般用于取名
                              RejectedExecutionHandler handler) { // 线程拒绝策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

A continuación se describen estos parámetros con un pequeño detalle y luego se analiza el uso de cada parámetro en combinación con el código fuente del proceso de ejecución del grupo de subprocesos.

int corePoolSize:线程池中的核心线程数量,即使没有任务,这些线程也不会销毁。
int maximumPoolSize:线程池中的最大线程数量,即线程池所支持创建的最大数量线程,即使任务超级多,也只会有 maximumPoolSize 数量的线程在运行。
long keepAliveTime:非核心线程的存活时间,当任务数量超过核心线程数量时,只要 corePoolSize < maximumPoolSize,线程池便会创建对应的线程数去执行任务,当线程池中存活的线程数量大于核心线程时,如果等了 keepAliveTime 时间仍然没有任务进来,则线程池会回收这些线程。
TimeUnit unit:非核心线程存活时间的具体单位,即等待多少毫秒、秒等。
BlockingQueue<Runnable> workQueue:存储线程任务所用的队列,提交的任务将会被放到该队列中。
ThreadFactory threadFactory:线程工厂,主要用来创建线程的时候给线程取名用,默认是pool-1-thread-3
RejectedExecutionHandler handler:线程拒绝策略,当存储任务所用的队列都被填满时,新来的任务此时无处存放,那么需要提供一种策略去解决这种情况。

Estado del grupo de subprocesos y métodos básicos

Esta clase define el estado básico y los métodos básicos de algunos grupos de subprocesos. Debes conocerlo antes de leer el código fuente:

/*
 * 该对象高3位维护线程池运行状态,低29位维护当前线程池数量
 */
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
/*
 * 该值等于29,用于左移
 */
private static final int COUNT_BITS = Integer.SIZE - 3;
/*
 * 线程池支持的最大线程数量,即29位所能表示的最大数值,2^29 - 1
 */
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
/*
 * 即高3位为111,低29位为0,该状态的线程池会接收新任务,并且处理阻塞队列中正在等待的任务
 */
private static final int RUNNING    = -1 << COUNT_BITS;
/*
 * 即高3位为000,不接受新任务,但仍会处理阻塞队列中正在等待的任务
 */
private static final int SHUTDOWN   =  0 << COUNT_BITS;
/*
 * 高3位为001,不接受新任务也不处理阻塞队列中的任务
 */
private static final int STOP       =  1 << COUNT_BITS;
/*
 * 高3位为010,所有任务都被终止了,workerCount为0,为此状态时还将调用terminated()方法
 */
private static final int TIDYING    =  2 << COUNT_BITS;
/*
 * 高3位为011,terminated()方法调用完成后变成此状态
 */
private static final int TERMINATED =  3 << COUNT_BITS;

Estos estados están representados por el tipo int, y la relación de tamaño es RUNNING <SHUTDOWN <STOP <TIDYING <TERMINATED, y esta secuencia básicamente sigue el proceso del grupo de subprocesos desde la ejecución hasta la terminación. Los siguientes tres métodos se utilizan comúnmente en esta clase para obtener el estado del grupo de subprocesos y el número de subprocesos:

/*
 * c & 高3位为1,低29位为0的~CAPACITY,用于获取高3位保存的线程池状态
 */
private static int runStateOf(int c)     { return c & ~CAPACITY; }
/*
 * c & 高3位为0,低29位为1的CAPACITY,用于获取低29位的线程数量
 */
private static int workerCountOf(int c)  { return c & CAPACITY; }
/*
 * 参数rs表示runState,参数wc表示workerCount,即根据runState和workerCount打包合并成ctl
 */
private static int ctlOf(int rs, int wc) { return rs | wc; }

El proceso de ejecución del grupo de subprocesos

La lógica de ejecución específica se encuentra principalmente en el método execute () de ThreadPoolExecutor . El código fuente es el siguiente:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        // ctl对象功能很强大,其高3位代表线程池的状态,低29位代表线程池中的线程数量
        int c = ctl.get();
        // 如果当前线程池中线程数量小于核心线程数,则新建一个线程并将当前任务直接赋予该线程执行
        if (workerCountOf(c) < corePoolSize) {
            // 如果新建线程成功则直接返回
            if (addWorker(command, true))
                return;
            // 到这一步说明新建失败,可能是线程池意外关闭或者是由于并发的原因导致当前线程数大于等于核心线程数了,重新获取ctl对象
            c = ctl.get();
        }
        // 如果当前线程处于运行态并且任务入队列成功,则继续执行下面的逻辑
        if (isRunning(c) && workQueue.offer(command)) {
            // 这里需要再次确认线程池是否仍处于运行态
            int recheck = ctl.get();
            // 如果非运行态则需要删除队列中的任务,然后拒绝该任务
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // 确保线程池中仍有运行的线程,如果都未存活则新建一个线程且不指定任务参数,让该线程自行去队列中获取任务执行
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 如果处于非运行态或者入队列不成功(队列满了),尝试扩容线程池线程数量至maxPoolSize,若扩容失败,则拒绝该任务
        else if (!addWorker(command, false))
            reject(command);
    }

Para resumir brevemente, se divide principalmente en tres pasos:

  1. Primero juzgue si el número de subprocesos que se están ejecutando actualmente es menor que el número de subprocesos centrales, de ser así, cree un nuevo subproceso para procesar la tarea y regrese directamente si el procesamiento es exitoso; de lo contrario, continúe juzgando
  2. Determine si el grupo de subprocesos actual se está ejecutando y si la tarea se puso en cola correctamente, y si es así, verifique dos veces para asegurarse de que todavía hay subprocesos en ejecución en el grupo.
  3. Si el segundo paso en la cola falla, el grupo de subprocesos intenta expandirse al número máximo de subprocesos, si falla, la tarea se rechaza

Dado que los pasos anteriores 1 y 3 deben bloquearse al agregar subprocesos, afectará el rendimiento, por lo que la mayoría de las operaciones del grupo de subprocesos se ejecutan en el segundo paso (dependiendo de la cantidad de subprocesos principales a admitir), a menos que el la cola de bloqueo ya no cabe. Se puede ver en el código anterior que incluso si se define la cantidad de subprocesos centrales, la cantidad de subprocesos corePoolSize no se crea de antemano, pero el bloqueo se agrega cada vez. Por lo tanto, por consideraciones de rendimiento, puede llamar a prestartAllCoreThreads () Método para iniciar el número de subprocesos de corePoolSize por adelantado.

Echemos un vistazo al método addWorker () para crear un nuevo hilo 

private boolean addWorker(Runnable firstTask, boolean core) {
        // 首先是一个外层死循环
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 检查当前线程池是否处于非运行态,同时确保队列中任务数不为空
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
            // 内存死循环修改运行的线程数量
            for (;;) {
                int wc = workerCountOf(c);
                // core参数确保不会超过线程池设定的值
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 采用CAS算法将线程数+1,如果成功则直接跳出外循环,失败主要是因为并发修改导致,那么则再次内循环判断
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 确保线程池运行状态没变,若发生改变,则从外循环开始判断
                c = ctl.get(); 
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            // 新建Worker内部类时主要干了两件事,一个是设置AQS同步锁标识为-1,另一个是调用线程工厂创建线程并赋值给Worker的成员变量
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                // 在往workers这个线程集合增加线程时需要进行加锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // 此处需要进行二次检查,防止线程池被关闭等异常情况
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 如果当前线程已经启动,处于活跃态则抛异常
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        // workers是一个HashSet集合
                        workers.add(w);
                        int s = workers.size();
                        // 设置最大池大小,同时标识任务线程增加成功,即 workerAdded 设为true
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                // 如果任务线程成功增加,则在此处启动线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

Parece ser muy largo, pero en realidad está haciendo algunos juicios básicos para prevenir situaciones anormales. Un breve resumen:

  1. Primero determine si está en el estado de ejecución y el número de subprocesos en ejecución actualmente es menor que el límite superior; de ser así, el algoritmo CAS agrega primero el número de subprocesos en ejecución
  2. Después de que la operación CAS sea exitosa, cree un nuevo subproceso de trabajador y agréguelo a la colección de subprocesos bloqueando, y luego inicie el subproceso

A continuación, observe la clase interna del trabajador . Esta clase envuelve el subproceso de la tarea, principalmente para controlar la interrupción del subproceso, es decir, cuando el grupo de subprocesos está cerrado, la tarea del subproceso correspondiente debe interrumpirse. La interrupción mencionada aquí está esperando para obtener el tarea de workQueue getTask () Solo se puede interrumpir, es decir, la interrupción se permite después de que el hilo realmente comienza a ejecutarse, por lo que el estado de bloqueo es un valor negativo (-1) durante la inicialización.

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** 由线程工厂所创建的线程对象 */
        final Thread thread;
        /** 业务任务对象 */
        Runnable firstTask;
        /** 当前线程所执行任务的计数 */
        volatile long completedTasks;

        /**
         * 初始化方法,主要是设置AQS的同步状态private volatile int state,是一个计数器,大于0代表锁已经被获取,设为-1后即禁止中断
         */
        Worker(Runnable firstTask) {
            setState(-1); // 将lock标识设为-1,
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        public void run() {
            runWorker(this);
        }

        /**
         * 下面的都是与锁相关的方法,state为0代表锁未被获取,1代表锁已经被获取
         */
        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;
            // 控制中断主要就是体现在中断前会判断 getState() >= 0
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

La propia clase Worker implementa Runnable y hereda AbstractQueuedSynchronizer, por lo que no solo es una tarea ejecutable, sino también el efecto de un bloqueo. Tenga en cuenta que el bloqueo es un bloqueo simple no reentrante

Finalmente, observe el método runWorker () que principalmente realiza tareas comerciales en el grupo de subprocesos:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        // 将state置为0,允许线程中断
        w.unlock(); 
        boolean completedAbruptly = true;
        try {
            // task为上层透传进来的指定业务线程,若为空则循环通过getTask()获取任务执行
            while (task != null || (task = getTask()) != null) {
                // 这里的加锁不是为了防止并发,而是为了在shutdown()时不终止正在运行的任务
                w.lock();
                // 双重检查防止线程池状态不大于stop且未被设为中断标识
                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);
        }
    }

 

Grupo de hilos incorporado

JDK tiene cuatro grupos de subprocesos integrados para nosotros, todos los cuales se crean a través de la clase de fábrica Ejecutores. No se recomienda crear grupos de subprocesos de esta manera. A continuación, se explicará por qué no.

newFixedThreadPool

Este es un grupo de subprocesos de longitud fija

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

Como su nombre lo indica, se crea un grupo de subprocesos de longitud fija. El número de subprocesos principales en el grupo de subprocesos es el mismo que el número máximo de subprocesos. Por lo tanto, el parámetro de tiempo de supervivencia del subproceso no principal no tiene sentido. El mayor problema es que la cola de bloqueo seleccionada es ilimitada. Infinitamente, seguirá agregando y agregando, eventualmente llenando la memoria. Se usa generalmente para servidores con cargas más pesadas y necesita limitar el número especificado de subprocesos.

newSingleThreadExecutor

Este es un grupo de subprocesos de un solo subproceso

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

Al observar los parámetros, también puede encontrar que solo se inicializa un subproceso y solo hay un subproceso trabajando en el grupo de principio a fin. Suena realmente lamentable. El único problema es que la cola de bloqueo no está limitada, lo que puede explotar la memoria Se utiliza generalmente para requisitos estrictos Escenarios que controlan el orden de ejecución de las tareas.

newCachedThreadPool

Este es un grupo de subprocesos almacenables en caché

 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

El número de subprocesos principales es 0 y el número máximo de subprocesos tiende a ser infinito, es decir, todos los subprocesos no centrales se utilizan para procesar tareas y el tiempo de supervivencia de cada subproceso es de 60 segundos. Tenga en cuenta que se utiliza una cola sincrónica aquí, es decir, solo se entregarán las tareas y no se guardarán. Tarea, una vez que haya una tarea, verifique si hay un subproceso inactivo, si no, cree un nuevo subproceso. Cuando la tasa de solicitud de tarea sea mayor que el tasa de procesamiento de subprocesos, la cantidad de subprocesos aumentará y, finalmente, la memoria explotará. Generalmente se usa para la simultaneidad Realiza una gran cantidad de pequeñas tareas a corto plazo, porque el subproceso que está inactivo durante 60 segundos se reciclará, por lo que el grupo de subprocesos que permanezca inactivo durante mucho tiempo no ocupará ningún recurso.

newScheduledThreadPool

Este también es un grupo de subprocesos de longitud fija, pero admite el tiempo y la ejecución de tareas periódicas

public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory, handler);
    }

Se puede ver que el número máximo de subprocesos sigue siendo infinito, por lo que habrá un problema de ráfaga de memoria hasta cierto punto. En el uso de la cola, se selecciona la cola de bloqueo retrasado DelayedWorkQueue, y la ejecución de la tarea específica también es más Complicado.

 

Supongo que te gusta

Origin blog.csdn.net/m0_38001814/article/details/107729317
Recomendado
Clasificación