Comprenda completamente el grupo de subprocesos de arriba a abajo desde Executor en 12 minutos

Prefacio

En el artículo anterior, hablamos sobre los componentes de sincronización de uso común en paquetes concurrentes y escribimos a mano un componente de sincronización personalizado en 13 minutos. Hablamos sobre los componentes de sincronización de uso común en paquetes concurrentes y también implementamos un componente de sincronización personalizado paso a paso.

Este artículo hablará sobre otro núcleo del paquete de concurrencia: grupo de subprocesos.

Se necesitan unos 12 minutos para leer este artículo.

Antes de leer este artículo, echemos un vistazo a algunas preguntas para ver si comprende el grupo de subprocesos.

  1. ¿Qué es la tecnología de pooling? ¿Qué características tiene y en qué escenarios se utiliza?
  2. ¿Qué es el ejecutor? ¿Cuál es su filosofía de diseño?
  3. ¿Cuántos tipos de tareas laborales existen? ¿Cuáles son las características? ¿Cómo adaptarse y luego entregárselo al Ejecutor?
  4. ¿Cómo se implementa el grupo de subprocesos? ¿Cuáles son los parámetros principales y cómo configurarlos? ¿Cuál es el flujo de trabajo?
  5. ¿Cómo maneja correctamente el grupo de subprocesos las excepciones? ¿Cómo desactivar el grupo de subprocesos?
  6. ¿Cómo se implementa el grupo de subprocesos que maneja el tiempo?

Tecnología de agrupación

La creación y destrucción de hilos traerá ciertos gastos generales.

Si creamos varios subprocesos cuando necesitamos usarlos y luego los destruimos después de usarlos, usarlos de esta manera no solo alargará el proceso comercial, sino que también aumentará la sobrecarga de crear y destruir subprocesos.

Entonces se nos ocurrió la idea de la tecnología de agrupación, donde los subprocesos se crean por adelantado y se administran en un grupo (contenedor).

Cuando sea necesario, tome un subproceso del grupo para realizar la tarea y vuelva a colocarlo en el grupo una vez completada la ejecución.

No solo los subprocesos tienen la idea de agrupar, sino que las conexiones también tienen la idea de agrupar, es decir, agrupar conexiones.

La puesta en común de tecnología no sólo reutiliza recursos y mejora la respuesta, sino que también facilita la gestión.

Marco ejecutor

¿Qué es el marco del Ejecutor?

El ejecutor puede considerarse temporalmente como una abstracción del grupo de subprocesos, que define cómo ejecutar tareas.

  public interface Executor {
      void execute(Runnable command);
  }

ExecutorSepare y desacople las tareas laborales de los grupos de subprocesos

imagen.png

Las tareas laborales se dividen en dos tipos: las que no arrojan resultados Runnabley las que sí.Callable

Ambas tareas están permitidas en el grupo de subprocesos, donde ambas son interfaces funcionales y se pueden implementar mediante expresiones lambda.

ExecutorAlgunos estudiantes pueden tener preguntas: ¿El método de ejecución definido en el marco anterior no permite solo Runnabletareas entrantes?

¿ Qué Callablemétodo llama la tarea para ejecutar?

FutureLa interfaz se utiliza para definir la obtención de resultados de tareas asincrónicas. Su clase de implementación suele serFutureTask

FutureTaskAl mismo tiempo que se implementa Runnable, también utiliza almacenamiento de campo yCallable , de hecho, realizará tareas cuando se implemente.RunnableCallable

Cuando el grupo de subprocesos ejecuta Callableuna tarea, la encapsulará FutureTaskpara Runnablesu ejecución (hablaremos sobre el código fuente específico más adelante), por lo que Executorel método de ejecución solo tieneRunnable

FutureTaskEquivalente a un adaptador, se convertirá Callabley Runnableluego se ejecutará.

imagen.png

El ejecutor define el grupo de subprocesos y su implementación importante esThreadPoolExecutor

Sobre ThreadPoolExecutorla base de, también hay un grupo de subprocesos para sincronizarScheduledThreadPoolExecutor

imagen.png

ThreadPoolEjecutor

Parámetros básicos

ThreadPoolExecutorHay siete parámetros principales importantes.

  public ThreadPoolExecutor(int corePoolSize,
                                int maximumPoolSize,
                                long keepAliveTime,
                                TimeUnit unit,
                                BlockingQueue<Runnable> workQueue,
                                ThreadFactory threadFactory,
                                RejectedExecutionHandler handler)
  1. corePoolSize El número de subprocesos principales en el grupo de subprocesos
  2. MaximumPoolSize El número máximo de subprocesos que el grupo de subprocesos permite crear.
  3. keepAliveTime timeout, unidad de tiempo TimeUnit: el tiempo que sobreviven los subprocesos no centrales después de estar inactivos
  4. workQueue almacena la cola de bloqueo esperando la ejecución de tareas
  5. threadFactory fábrica de subprocesos: especifica cómo crear subprocesos y puede especificar diferentes nombres de grupos de subprocesos según diferentes negocios
  6. Estrategia de rechazo de RejectedExecutionHandler: cómo rechazar tareas cuando no hay suficientes subprocesos y la cola de bloqueo está llena
Denegar política efecto
Valor predeterminado de política de aborto lanzar una excepción
Política de ejecuciones de llamadas Llamar a un hilo para realizar una tarea.
Descartar Política No procesar, descartar
Descartar política más antigua Descartar la última tarea en la cola y ejecutar la tarea actual inmediatamente

Además de los parámetros principales durante la construcción, el grupo de subprocesos también utiliza clases internas Workerpara encapsular subprocesos y tareas, y utiliza la workescola de trabajo del contenedor HashSet para almacenar subprocesos de trabajo.

Principio de implementación

diagrama de flujo

Para comprender claramente el principio de implementación del grupo de subprocesos, primero describimos el principio con un diagrama de flujo y un resumen, y finalmente analizamos la implementación del código fuente.

imagen.png

  1. Si la cantidad de subprocesos de trabajo es menor que la cantidad de subprocesos principales, cree subprocesos, únase a la cola de trabajo y ejecute tareas
  2. Si la cantidad de subprocesos de trabajo es mayor o igual que la cantidad de subprocesos principales y el grupo de subprocesos aún se está ejecutando, intente agregar la tarea a la cola de bloqueo.
  3. Si la tarea no puede unirse a la cola de bloqueo (lo que indica que la cola de bloqueo está llena) y la cantidad de subprocesos de trabajo es menor que la cantidad máxima de subprocesos, cree un subproceso para su ejecución.
  4. Si la cola de bloqueo está llena y el número de subprocesos de trabajo alcanza el número máximo de subprocesos, se ejecuta la política de rechazo.
ejecutar

El grupo de subprocesos tiene dos métodos de envío: ejecutar y enviar. El envío se encapsulará en un RunnableFuture y finalmente se ejecutará.execute

      public <T> Future<T> submit(Callable<T> task) {
          if (task == null) throw new NullPointerException();
          RunnableFuture<T> ftask = newTaskFor(task);
          execute(ftask);
          return ftask;
      }

executeImplemente todo el proceso de ejecución del grupo de subprocesos en

  public void execute(Runnable command) {
      //任务为空直接抛出空指针异常
      if (command == null)
          throw new NullPointerException();
      //ctl是一个整型原子状态,包含workerCount工作线程数量 和 runState是否运行两个状态
      int c = ctl.get();
      //1.如果工作线程数 小于 核心线程数 addWorker创建工作线程
      if (workerCountOf(c) < corePoolSize) {
          if (addWorker(command, true))
              return;
          c = ctl.get();
      }
      
      // 2.工作线程数 大于等于 核心线程数时
      // 如果 正在运行 尝试将 任务加入队列
      if (isRunning(c) && workQueue.offer(command)) {
          //任务加入队列成功 检查是否运行
          int recheck = ctl.get();
          //不在运行 并且 删除任务成功 执行拒绝策略 否则查看工作线程为0就创建线程
          if (! isRunning(recheck) && remove(command))
              reject(command);
          else if (workerCountOf(recheck) == 0)
              addWorker(null, false);
      }
      // 3.任务加入队列失败,尝试去创建非核心线程,成功则结束
      else if (!addWorker(command, false))
          // 4.失败则执行拒绝策略
          reject(command);
  }
agregar trabajador

addWorkerSe utiliza para crear subprocesos para unirse a la cola de trabajos y ejecutar tareas.

El segundo parámetro se utiliza para determinar si se crea un subproceso principal: es verdadero cuando se crea un subproceso principal y falso cuando se crea un subproceso no principal.

  private boolean addWorker(Runnable firstTask, boolean core) {
          //方便跳出双层循环
          retry:
          for (;;) {
              int c = ctl.get();
              int rs = runStateOf(c);
  
              // Check if queue empty only if necessary.
              // 检查状态
              if (rs >= SHUTDOWN &&
                  ! (rs == SHUTDOWN &&
                     firstTask == null &&
                     ! workQueue.isEmpty()))
                  return false;
  
              for (;;) {
                  int wc = workerCountOf(c);
                  //工作线程数已满 返回false 
                  if (wc >= CAPACITY ||
                      wc >= (core ? corePoolSize : maximumPoolSize))
                      return false;
                  //CAS自增工作线程数量 成功跳出双重循环
                  if (compareAndIncrementWorkerCount(c))
                      break retry;
                  //CAS失败 重新读取状态 内循环
                  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 {
              //创建worker 通过线程工厂创建线程
              w = new Worker(firstTask);
              final Thread t = w.thread;
              if (t != null) {
                  //全局锁
                  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;
      }

addWorkerZhonghui CAS aumenta automáticamente la cantidad de subprocesos de trabajo, crea subprocesos y luego los bloquea, agrega los subprocesos a la cola de trabajo (hashset) y, después de desbloquearlos, inicia el subproceso para realizar tareas.

ejecutar trabajador

Lo que se implementa en el trabajador Runnablees runWorkerun método. Después de iniciar el hilo, continuará ejecutando la tarea. Después de ejecutar la tarea, obtendrá la ejecución de la tarea.

  final void runWorker(Worker w) {
      Thread wt = Thread.currentThread();
      Runnable task = w.firstTask;
      w.firstTask = null;
      w.unlock(); // allow interrupts
      boolean completedAbruptly = true;
      try {
          //循环执行任务 getTask获取任务
          while (task != null || (task = getTask获取任务()) != null) {
              w.lock();
              // 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);
      }
  }

Reserve dos métodos de enlace vacíos antes y después de la ejecución, dejándolos para que las subclases los expandan. También se usarán más adelante para manejar excepciones del grupo de subprocesos.

Parámetros de configuración

¿Cuantos más subprocesos haya en el grupo de subprocesos, mejor?

En primer lugar, debemos comprender que la creación de un subproceso conlleva una sobrecarga: el contador del programa, la pila de la máquina virtual y la pila de métodos locales son espacios privados del subproceso.

Y cuando un subproceso solicita espacio, solicita una parte de la memoria en el área del Edén de la generación joven a través de CAS (se requiere CAS porque puede haber varios subprocesos solicitando al mismo tiempo)

Demasiados subprocesos pueden hacer que se utilice demasiado espacio de Eden, lo que genera gc jóvenes, y el cambio de contexto de subprocesos también requiere una sobrecarga.

Por lo tanto, cuantos más subprocesos haya en el grupo de subprocesos, mejor. La industria se divide en dos soluciones generales:

Para aplicaciones con uso intensivo de CPU, el grupo de subprocesos establece la cantidad máxima de subprocesos en la cantidad de núcleos de CPU + 1 para evitar el cambio de contexto, mejorar el rendimiento y dejar un subproceso más para proteger el resultado final.

Para situaciones con uso intensivo de IO, el grupo de subprocesos establece el número máximo de subprocesos en 2 veces el número de núcleos de CPU. Dado que IO debe esperar, se necesitan más subprocesos para evitar la CPU inactiva.

Los escenarios comerciales específicos requieren un análisis detallado y luego se agregan una gran cantidad de pruebas para obtener la configuración más razonable.

El marco Executor proporciona varios grupos de subprocesos a través de métodos de fábrica estáticos, como: Executors.newSingleThreadExecutor(), Executors.newFixedThreadPool(),Executors.newCachedThreadPool()

Sin embargo, debido a diferentes escenarios comerciales, es mejor personalizar el grupo de subprocesos; después de comprender los parámetros del grupo de subprocesos y los principios de implementación, no es difícil ver su código fuente. No daremos más detalles.

Manejar excepciones

¿Qué sucede si ocurre una excepción en el grupo de subprocesos?

Ejecutable

Cuando usamos Runnabletareas, las excepciones se lanzarán directamente

         threadPool.execute(() -> {
             int i = 1;
             int j = 0;
             System.out.println(i / j);
         });

Ante esta situación, podemos usar try-catch en la tarea Runnable para capturar

         threadPool.execute(() -> {
             try {
                 int i = 1;
                 int j = 0;
                 System.out.println(i / j);
             } catch (Exception e) {
                 System.out.println(e);
             }
         });

Para operaciones reales, utilice el registro en lugar de imprimir en la consola.

invocable

Cuando usamos Callabletareas, usando el método de envío obtendremosFuture

         Future<Integer> future = threadPool.submit(() -> {
             int i = 1;
             int j = 0;
             return i / j;
         });

Si no lo usa Future.get()para obtener el valor de retorno, no se lanzará la excepción, lo cual es más peligroso.

¿Por qué existe tal situación?

Como se mencionó anteriormente, al ejecutar el envío, se encapsulará Callableen FutureTaskejecución.

En su implementación de Runnable, al ejecutar la tarea Callable, si ocurre una excepción, se encapsulará en FutureTask.

     public void run() {
         //...其他略
         try {
             //执行call任务
             result = c.call();
             ran = true;
         } catch (Throwable ex) {
             //出现异常 封装到FutureTask
             result = null;
             ran = false;
             setException(ex);
         }
         //..
     }

Cuando se ejecuta get, primero bloquea y luego juzga el estado hasta que se completa la tarea. Si el estado es anormal, se lanza una excepción encapsulada.

     private V report(int s) throws ExecutionException {
         Object x = outcome;
         if (s == NORMAL)
             return (V)x;
         if (s >= CANCELLED)
             throw new CancellationException();
         throw new ExecutionException((Throwable)x);
     }

Por lo tanto, al procesar Callableuna tarea, puede capturar la tarea u obtenerla.

         //捕获任务
         Future<?> f = threadPool.submit(() -> {
             try {
                 int i = 1;
                 int j = 0;
                 return i / j;
             } catch (Exception e) {
                 System.out.println(e);
             } finally {
                 return null;
             }
         });
 ​
         //捕获get
         Future<Integer> future = threadPool.submit(() -> {
             int i = 1;
             int j = 0;
             return i / j;
         });
 ​
         try {
             Integer integer = future.get();
         } catch (Exception e) {
             System.out.println(e);
         }
después del ejecutor

¿ Recuerdas el grupo de subprocesos runWorker?

Obtiene continuamente la ejecución de tareas en la cola de bloqueo en un bucle y reserva métodos de enlace antes y después de la ejecución.

Heredar ThreadPoolExecutorpara reescribir el método de enlace después de la ejecución, registrar si ocurre una excepción después de la ejecución y, si hay una excepción, registrarla para crear una capa de plan de encubrimiento.

 public class MyThreadPool extends ThreadPoolExecutor {  
     //...
     
     @Override
     protected void afterExecute(Runnable r, Throwable t) {
         //Throwable为空 可能是submit提交 如果runnable为future 则捕获get
         if (Objects.isNull(t) && r instanceof Future<?>) {
             try {
                 Object res = ((Future<?>) r).get();
             } catch (InterruptedException e) {
                 Thread.currentThread().interrupt();
             } catch (ExecutionException e) {
                 t = e;
             }
         }
 ​
         if (Objects.nonNull(t)) {
             System.out.println(Thread.currentThread().getName() + ": " + t.toString());
         }
     }
 }

De esta manera, incluso si usa enviar y olvida usar obtener, la excepción no "desaparecerá".

establecerExcepción no detectada

Al crear un hilo, puede establecer un uncaughtExceptionmétodo de excepción no detectada, que se llamará cuando se produzca una excepción no detectada en el hilo . También puede imprimir un registro para obtener información completa.

Definimos nuestra propia fábrica de subprocesos, creamos subprocesos basados ​​en grupos de grupos empresariales (para facilitar la resolución de problemas) y establecemos uncaughtExceptionmétodos.

 public class MyThreadPoolFactory implements ThreadFactory {
 ​
     private AtomicInteger threadNumber = new AtomicInteger(1);
     
     private ThreadGroup group;
 ​
     private String namePrefix = "";
 ​
     public MyThreadPoolFactory(String group) {
         this.group = new ThreadGroup(group);
         namePrefix = group + "-thread-pool-";
     }
 ​
 ​
     @Override
     public Thread newThread(Runnable r) {
         Thread t = new Thread(group, r,
                 namePrefix + threadNumber.getAndIncrement(),
                 0);
         t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
             @Override
             public void uncaughtException(Thread t, Throwable e) {
                 System.out.println(t.getName() + ":" + e);
             }
         });
 ​
         if (t.isDaemon()) {
             t.setDaemon(false);
         }
         if (t.getPriority() != Thread.NORM_PRIORITY) {
             t.setPriority(Thread.NORM_PRIORITY);
         }
         return t;
     }
 ​
 }

Cerrar grupo de hilos

2 formas de cerrar el grupo de subprocesos:shutdown(),shutdownNow()

Sus principios son: atravesar los subprocesos en los trabajadores de la cola de trabajo, interrumpir uno por uno (llamar al método del subproceso interrupt) y es posible que las tareas que no puedan responder a las interrupciones nunca finalicen.

La tarea de apagado se ejecutará.

  1. Establecer el estado del grupo de subprocesos en APAGADO
  2. Interrumpir todos los subprocesos que no estén ejecutando tareas.

Es posible que la tarea ShutdownNow no se complete

  1. Establecer el estado del grupo de subprocesos en DETENER
  2. Intente detener todos los subprocesos que estén ejecutando o suspendiendo tareas
  3. Volver a la lista de tareas pendientes de ejecutar

Generalmente se usa el apagado, si no es necesario completar la tarea, se puede usar el apagado Ahora.

SecheduledThreadPoolExecutor

ScheduledThreadPoolExecutorThreadPoolExecutorProporciona funciones de ejecución programadas basadas en

Tiene dos métodos de cronometraje.

scheduleAtFixedRateTomando el inicio de la tarea como punto de partida del ciclo , por ejemplo, una tarea tarda 0,5 s en ejecutarse y se ejecuta cada 1 s, lo que equivale a iniciar la tarea después de 0,5 s después de completarla.

scheduledWithFixedDelayTomando el final de la tarea como punto de partida del ciclo , por ejemplo, una tarea tarda 0,5 segundos en ejecutarse y se ejecuta cada 1 segundo, lo que equivale a iniciar la tarea 1 segundo después de que se completa la tarea.

         ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(2);
         //scheduleAtFixedRate 固定频率执行任务 周期起点为任务开始
         scheduledThreadPoolExecutor.scheduleAtFixedRate(()->{
             try {
                 TimeUnit.SECONDS.sleep(1);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("scheduleAtFixedRate 周期起点为任务开始");
             //初始延迟:1s  周期:1s
         },1,1, TimeUnit.SECONDS);
 ​
         //scheduledWithFixedDelay 固定延迟执行任务,周期起点为任务结束
         scheduledThreadPoolExecutor.scheduleWithFixedDelay(()->{
             try {
                 TimeUnit.SECONDS.sleep(1);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("scheduledWithFixedDelay 周期起点为任务结束 ");
             //初始延迟:1s  周期:1s
         },1,1, TimeUnit.SECONDS);

El grupo de subprocesos de sincronización utiliza una cola de retraso para actuar como una cola de bloqueo.

La cola de retraso es una cola de prioridad que clasifica y almacena las tareas programadas. Cuanto más corto sea el tiempo, antes se ejecutarán.

Cuando un hilo obtiene una tarea, obtendrá la tarea programada de la cola de retraso y la ejecutará si el tiempo ha expirado.

     public RunnableScheduledFuture<?> take() throws InterruptedException {
             final ReentrantLock lock = this.lock;
             lock.lockInterruptibly();
             try {
                 for (;;) {
                     RunnableScheduledFuture<?> first = queue[0];
                     //没有定时任务 等待
                     if (first == null)
                         available.await();
                     else {
                         //获取延迟时间
                         long delay = first.getDelay(NANOSECONDS);
                         //小于等于0 说明超时,拿出来执行
                         if (delay <= 0)
                             return finishPoll(first);
                         first = null; // don't retain ref while waiting
                         //当前线程是leader则等待对应的延迟时间,再进入循环取出任务执行
                         //不是leader则一直等待,直到被唤醒
                         if (leader != null)
                             available.await();
                         else {
                             Thread thisThread = Thread.currentThread();
                             leader = thisThread;
                             try {
                                 available.awaitNanos(delay);
                             } finally {
                                 if (leader == thisThread)
                                     leader = null;
                             }
                         }
                     }
                 }
             } finally {
                 if (leader == null && queue[0] != null)
                     available.signal();
                 lock.unlock();
             }
         }

Uno de estos dos métodos de cronometraje toma el inicio de la tarea como punto de partida del ciclo y el otro toma el final de la tarea como punto de partida del ciclo.

El proceso de obtención de tareas programadas es el mismo, excepto que el tiempo de demora en las tareas programadas que construyen es diferente.

La diferencia entre el uso de tareas programadas periodes que el punto de inicio del período es el comienzo de la tarea cuando es un número positivo, y el punto de inicio del período es el final de la tarea cuando es un número negativo.

Resumir

Este artículo se centra en el grupo de subprocesos y explica en términos simples la tecnología de agrupación, el ejecutor, los parámetros del grupo de subprocesos, la configuración, los principios de implementación, el manejo de excepciones, el apagado, etc.

El uso de tecnología de agrupación puede ahorrar la sobrecarga de creación y cierre frecuentes, mejorar la velocidad de respuesta y facilitar la administración. A menudo se usa en grupos de subprocesos, grupos de conexiones, etc.

El marco Executor desacopla y separa las tareas de trabajo de la ejecución (grupo de subprocesos), y las tareas de trabajo se dividen en aquellas sin valores de retorno Runnabley aquellas con valores de retorno.Callable

En realidad, el ejecutor solo procesa Runnabletareas y Callablelas encapsula en ejecución FutureTaskadaptativa .Runnable

El grupo de subprocesos utiliza colas de trabajo para administrar subprocesos. Después de que el subproceso ejecuta la tarea, tomará la tarea de la cola de bloqueo para su ejecución. Cuando el subproceso no central esté inactivo durante un cierto período de tiempo, se cerrará.

Cuando se ejecuta el grupo de subprocesos, si la cantidad de subprocesos de la cola de trabajo es menor que la cantidad de subprocesos principales, se crean subprocesos para ejecutar (bastante calentados)

Si el número de subprocesos de la cola de trabajo es mayor que el número de subprocesos principales y la cola de bloqueo no está llena, se coloca en la cola de bloqueo.

Si la cola de bloqueo está llena y no se ha alcanzado el número máximo de subprocesos, se crea un subproceso no principal para realizar la tarea.

Utilice la política de denegación si se ha alcanzado el número máximo de subprocesos

Parámetros de configuración: el tipo de uso intensivo de CPU es el número de núcleos de CPU + 1; el tipo de uso intensivo de IO es 2 veces el número de núcleos de CPU; es necesario probar la configuración específica.

Para manejar excepciones, puede capturar tareas directamente, Callablecapturar obtener o heredar el grupo de subprocesos para afterExecutorregistrar excepciones. También puede configurar el método para manejar excepciones no detectadas al crear un subproceso.

El grupo de subprocesos que procesa las tareas programadas se implementa mediante una cola de retraso. Cuanto más cortas sean las tareas programadas, antes se ejecutarán. El subproceso obtendrá las tareas programadas de la cola de retraso (cuando el tiempo haya expirado) y esperará antes de que finalice el tiempo. depende.

Finalmente (no lo hagas gratis, solo presiona tres veces seguidas para pedir ayuda~)

Este artículo se incluye en la columna " De punto a línea y de línea a superficie" para construir un sistema de conocimiento de programación concurrente Java en términos simples . Los estudiantes interesados ​​​​pueden seguir prestando atención.

Las notas y los casos de este artículo se han incluido en gitee-StudyJava y github-StudyJava . Los estudiantes interesados ​​pueden continuar prestando atención en stat ~

Dirección del caso:

Gitee-JavaConcurrentProgramming/src/main/java/D_ThreadPool

Github-JavaConcurrentProgramming/src/main/java/D_ThreadPool

Si tiene alguna pregunta, puede discutirla en el área de comentarios. Si cree que la escritura de Cai Cai es buena, puede darle me gusta, seguirla y recopilarla para respaldarla ~

Siga a Cai Cai y comparta más información útil, cuenta pública: la cocina privada back-end de Cai Cai

¡Este artículo es publicado por OpenWrite, un blog que publica varios artículos !

Lei Jun: La versión oficial del nuevo sistema operativo de Xiaomi, ThePaper OS, ha sido empaquetada. Una ventana emergente en la página de lotería de la aplicación Gome insulta a su fundador. El gobierno de Estados Unidos restringe la exportación de la GPU NVIDIA H800 a China. La interfaz de Xiaomi ThePaper OS está expuesto. Un maestro usó Scratch para frotar el simulador RISC-V y se ejecutó con éxito. Kernel de Linux Escritorio remoto RustDesk 1.2.3 lanzado, soporte mejorado para Wayland Después de desconectar el receptor USB de Logitech, el kernel de Linux falló Revisión aguda de DHH de "herramientas de empaquetado ": no es necesario construir la interfaz en absoluto (Sin compilación) JetBrains lanza Writerside para crear documentación técnica Herramientas para Node.js 21 lanzadas oficialmente
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/6903207/blog/10109044
Recomendado
Clasificación