Hable sobre el estado del grupo de subprocesos de Java ThreadPoolExecutor (dos)

Sabemos que una vez que llega una tarea, el grupo de subprocesos asignará subprocesos para procesar la tarea o colocará la tarea en la cola de caché. Entonces, ¿cómo maneja el grupo de subprocesos las tareas de manera eficiente? Varias tareas ingresan a la cola de caché y las tareas de recuperación de subprocesos múltiples. ¿Cómo se resuelven los problemas de simultaneidad? Los subprocesos tienen estado, entonces, ¿el grupo de subprocesos tiene estado? Echémosle un vistazo poco a poco.

En primer lugar, hablemos de los atributos básicos del estado del grupo de subprocesos.

Estado del grupo de subprocesos: hay cinco estados en el grupo de subprocesos, y estos cinco estados están encapsulados hasta cierto punto. Son:

  • EN EJECUCIÓN: puede aceptar tareas recién enviadas y también puede procesar tareas en la cola de bloqueo, que se está ejecutando.
  • APAGADO: estado cerrado, ya no acepta tareas enviadas recientemente, pero puede continuar procesando las tareas guardadas en la cola de bloqueo. Cuando el grupo de subprocesos está en el estado EJECUTANDO, llamar al método shutdown () hará que el grupo de subprocesos entre en este estado. Lo encontré útil en el código del artículo anterior.
  • DETENER: No puede aceptar nuevas tareas, no procesar tareas en la cola e interrumpir el hilo que está procesando tareas. Cuando el grupo de subprocesos está en el estado EJECUTANDO o APAGADO, llamar al método shutdownNow () hará que el grupo de subprocesos entre en este estado. Es mejor no llamarlo directamente. Si algunas tareas no se procesan, se lanzará una excepción.
  • TIDYING: Si todas las tareas han finalizado, workerCount (el número de subprocesos efectivos) es 0, y el grupo de subprocesos llamará al método terminated () para ingresar al estado TERMINATED después de ingresar a este estado.
  • TERMINATED: Ingrese a este estado después de que se ejecute el método terminated (). Por defecto, no se hace nada en el método terminated ().

Echemos un vistazo al código:

   private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
     // 计数位 29
	private static final int COUNT_BITS = Integer.SIZE - 3;
	// 左移29位然后-1,0001 1111 1111 1111 1111 1111 1111 1111
	private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
	
	// 五种线程状态
	//-1左移29位,也就是-536870912
	//二进制展示是1010 0000 0000 0000 0000 0000 0000 0000
    private static final int RUNNING    = -1 << COUNT_BITS;
	//二进制展示是0000 0000 0000 0000 0000 0000 0000 0000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
	//二进制展示是0010 0000 0000 0000 0000 0000 0000 0000
    private static final int STOP       =  1 << COUNT_BITS;
	//二进制展示是0100 0000 0000 0000 0000 0000 0000 0000
    private static final int TIDYING    =  2 << COUNT_BITS;
	//二进制展示是0110 0000 0000 0000 0000 0000 0000 0000
    private static final int TERMINATED =  3 << COUNT_BITS;

    // 获取线程状态
	//~代表非,那么~CAPACITY也就是1110 0000 0000 0000 0000 0000 0000 0000
	// c 假设是                   1010 0000 0000 0000 0000 0000 0000 0000
	// 那么返回的就是             1010 0000 0000 0000 0000 0000 0000 0000
    private static int runStateOf(int c)     { return c & ~CAPACITY; }

El grupo de subprocesos usa ctl para controlar el estado de ejecución del grupo de subprocesos y el número de subprocesos efectivos en el grupo de subprocesos. Se usa AtomicInteger seguro para subprocesos. Ctl tiene un total de 32 bits. Los primeros tres bits son los bits de estado, y los últimos 29 bits son el recuento del número de subprocesos. Es decir, 2 ^ 29-1 = 536870912, que es más de 500 millones, que es mucho. Si supera tantos subprocesos, se estima que no poder sostenerlo. No debe usarse tanto. La CPU es GG, O (∩_∩) O jaja ~.

Podemos ver que el estado del hilo está representado por un número de 29 dígitos desplazado a la izquierda por -1,0,1,2,3. Nota: Nunca es -1,0,1,2,3. Si Tú lo dices, sabes que no he leído el código fuente. Lo miré y comencé a hablar. . Ladrón avergonzado

Luego, al obtener el estado, se puede obtener directamente a través de c & ~ CAPACITY, y la cantidad también se puede obtener directamente a través de ctl. De esta manera, ¿es el diseño un buen papel de muestra? Jeje

Echemos un vistazo a la operación.

    public void execute(Runnable command) {
	    // 运行的对象都没有,只能抛出异常了
        if (command == null)
            throw new NullPointerException();
		// 获取ctl的value,线程状态和workerCount都在里面,没它不行啊
        int c = ctl.get();
		//上面说了获取线程状态。workerCountOf(c) 这个就是获取线程数量了
		//小于核心线程数,那就分配线程直接运行呗
        if (workerCountOf(c) < corePoolSize) {
			// 添加到任务
            if (addWorker(command, true))
                return;
			//添加任务失败,这个时候ctl也许会改变,需要重新获取ctl
            c = ctl.get();
        }
		//如果当前线程是运行状态并且添加任务到列表成功
        if (isRunning(c) && workQueue.offer(command)) {
			// 获取ctl
            int recheck = ctl.get();
			//再次判断线程池的运行状态,如果不是运行状态,把刚刚加到workQueue中的command移除
            if (! isRunning(recheck) && remove(command))
				// 拒绝策略
                reject(command);
			//有效线程数为0,那就要执行addWork方法了
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
		//再次调用addWorker方法,但第二个参数传入为false,
		//将线程池的有限线程数量的上限设置为maximumPoolSize;
        else if (!addWorker(command, false))
            reject(command);
    }

El anterior es el método principal para ejecutar tareas que se ejecutan en el grupo de subprocesos, y se ha escrito un comentario simple. Este código se divide principalmente en tres pasos.

  1. Si el número de subprocesos en ejecución es menor que corePoolSize, utilice subprocesos directamente para realizar tareas
  2. Si la tarea se puso en cola correctamente, entonces aún es necesario verificar si debe unirse al hilo, porque es posible que el hilo que se acaba de verificar se haya ejecutado.
  3. Si no puede ingresar a la cola de caché, la cola de caché está llena y solo puede reiniciar el subproceso. Si el subproceso máximo lo permite, si el subproceso no se inicia, la tarea solo se puede rechazar.

Veamos el método principal addWorker ~

    private boolean addWorker(Runnable firstTask, boolean core) {
		//retry 这个是标志位,就是在循环中continue,break的时候可以进行多层跳出
		//个人觉得这段代码使用while处理可能更亲切一点,哈哈
        retry:
		// 无限循环,不多说
        for (;;) {
			//获取ctl以及状态
            int c = ctl.get();
            int rs = runStateOf(c);

            // 线程池的状态是int类型,从运行到关闭是逐渐增大的,所以直接使用大于小于对比
			// rs >= SHUTDOWN,表示此时不再接收新任务
			// rs == SHUTDOWN,这时表示关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务
		    // 既然不接受新任务,那 firstTask当然要为空
			// 如果workQueue不为空,就是处理workQueue里面的任务,没问题,但是加了一个否定,那还处理啥?
            if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
                return false;

            for (;;) {
				// 工作的线程的计数,就是第几个工作的线程
                int wc = workerCountOf(c);
				// 超出最大限制(默认2亿多,这个是可以初始化设置的,不会真是这么多)还搞啥?GG,
				// 根据core判断,是取corePoolSize还是maximumPoolSize,超过了线程数目,当然有也GG
                if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
				// 尝试增加workerCount,成功的话就跳出循环
                if (compareAndIncrementWorkerCount(c))
					//跳出retry下面的这个循环
                    break retry;
				// 重新获取ctl	
                c = ctl.get(); 
				// 获取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 {
			// 新建一个worker
            w = new Worker(firstTask);
			// 获取worker的线程
			//这个ThreadFactory如果没设置的话,会默认使用默认java.util.concurrent.Executors.DefaultThreadFactory
            final Thread t = w.thread;
	        //线程肯定不能为空,为空的话那就增加任务失败,执行不了呗
            if (t != null) {
				//获取新的ReentrantLock锁(排它锁)
                final ReentrantLock mainLock = this.mainLock;
				//加锁,不多说哈,不熟悉的可以看下之前锁的讲解
				// 因为这是一个线程池,肯定存在多线程竞争的情况,比如同时去取一个任务,同时去执行等等
                mainLock.lock();
                try {
                    //获取ctl状态
                    int rs = runStateOf(ctl.get());
					// rs < SHUTDOWN表示是RUNNING
					// 如果rs是RUNNING状态或者rs是SHUTDOWN状态并且firstTask为null,继续下面的逻辑
					// SHUTDOWN状态下,不会添加新的任务,只会执行缓存列表中的任务
                    if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {
						//预检查t是否可启动
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
						// private final HashSet<Worker> workers = new HashSet<Worker>();
						// workers是一个HashSet
                        workers.add(w);
						// largestPoolSize 线程池的最大线程数量
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
				// 任务增加成功,也分配到线程了
                if (workerAdded) {
				    // 那就启动线程,运行任务呗
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
			// 启动失败的话,就把已经加入到workers里面的任务移除掉
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

addWorker tiene solo dos parámetros de entrada, firstTask y core, es decir, addworker (agregar tarea) solo se preocupa por el tamaño de la tarea y el grupo de subprocesos, y el tamaño del grupo de subprocesos se determina de acuerdo con los parámetros pasados ​​por el núcleo.

Para ingresar a este método, lo primero es filtrar el estado. Hay 5 estados en el grupo de subprocesos. A excepción de RUNNING y SHOWDOWN, otros estados ya no ejecutarán tareas, y el estado SHUTDOWN solo ejecuta tareas en la cola de caché, así que addWorker Primero juzgué esto.

A continuación, es el juicio del número de subprocesos. El número de subprocesos no debe exceder el número máximo de subprocesos permitidos por el método. Si excede el número máximo de subprocesos, ningún subproceso puede manejar la tarea, es decir, lo hará directamente GG y devuelve falso.

Por supuesto, si el estado cambia en medio de la ejecución, por ejemplo, llamando a shutdown o llamando al método shutdownNow, debe volver a pasar por el ciclo for.

Ahora que las comprobaciones anteriores están bien, comenzaremos a agregar tareas.

Cree una nueva tarea, adquiera un hilo y luego agregue un candado, un candado exclusivo. Debido a que el grupo de subprocesos es multiproceso, existe y encuentra elefantes, y el HashSet de trabajadores no es seguro para subprocesos, por lo que debe bloquearse.

Una vez que se agrega el bloqueo, el trabajador se agrega correctamente a los trabajadores, lo que significa que la tarea se agrega correctamente y se puede iniciar después.

Al iniciar, es directamente t.start (). Puedes ver cómo vino esto. Se toma del trabajador, el método de construcción del trabajador.

Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
    this.thread = getThreadFactory().newThread(this); 这个线程中的runnable,其实就是传进来的firstTask!所以t.strat(),也就是运行firstTask。
    大致的流程就是这样哈。

      Está casi terminado, veámoslo de nuevo la próxima vez ~

Sin sacrificio, sin victoria ~

 

Supongo que te gusta

Origin blog.csdn.net/zsah2011/article/details/110230276
Recomendado
Clasificación