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.
- ¿Qué es la tecnología de pooling? ¿Qué características tiene y en qué escenarios se utiliza?
- ¿Qué es el ejecutor? ¿Cuál es su filosofía de diseño?
- ¿Cuántos tipos de tareas laborales existen? ¿Cuáles son las características? ¿Cómo adaptarse y luego entregárselo al Ejecutor?
- ¿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?
- ¿Cómo maneja correctamente el grupo de subprocesos las excepciones? ¿Cómo desactivar el grupo de subprocesos?
- ¿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);
}
Executor
Separe y desacople las tareas laborales de los grupos de subprocesos
Las tareas laborales se dividen en dos tipos: las que no arrojan resultados Runnable
y 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.
Executor
Algunos estudiantes pueden tener preguntas: ¿El método de ejecución definido en el marco anterior no permite solo Runnable
tareas entrantes?
¿ Qué Callable
método llama la tarea para ejecutar?
Future
La interfaz se utiliza para definir la obtención de resultados de tareas asincrónicas. Su clase de implementación suele serFutureTask
FutureTask
Al mismo tiempo que se implementa Runnable
, también utiliza almacenamiento de campo yCallable
, de hecho, realizará tareas cuando se implemente.Runnable
Callable
Cuando el grupo de subprocesos ejecuta Callable
una tarea, la encapsulará FutureTask
para Runnable
su ejecución (hablaremos sobre el código fuente específico más adelante), por lo que Executor
el método de ejecución solo tieneRunnable
FutureTask
Equivalente a un adaptador, se convertirá Callable
y Runnable
luego se ejecutará.
El ejecutor define el grupo de subprocesos y su implementación importante esThreadPoolExecutor
Sobre ThreadPoolExecutor
la base de, también hay un grupo de subprocesos para sincronizarScheduledThreadPoolExecutor
ThreadPoolEjecutor
Parámetros básicos
ThreadPoolExecutor
Hay siete parámetros principales importantes.
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize El número de subprocesos principales en el grupo de subprocesos
- MaximumPoolSize El número máximo de subprocesos que el grupo de subprocesos permite crear.
- keepAliveTime timeout, unidad de tiempo TimeUnit: el tiempo que sobreviven los subprocesos no centrales después de estar inactivos
- workQueue almacena la cola de bloqueo esperando la ejecución de tareas
- threadFactory fábrica de subprocesos: especifica cómo crear subprocesos y puede especificar diferentes nombres de grupos de subprocesos según diferentes negocios
- 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 Worker
para encapsular subprocesos y tareas, y utiliza la workes
cola 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.
- 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
- 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.
- 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.
- 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;
}
execute
Implemente 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
addWorker
Se 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;
}
addWorker
Zhonghui 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 Runnable
es runWorker
un 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 Runnable
tareas, 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 Callable
tareas, 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á Callable
en FutureTask
ejecució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 Callable
una 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 ThreadPoolExecutor
para 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 uncaughtException
mé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 uncaughtException
mé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á.
- Establecer el estado del grupo de subprocesos en APAGADO
- Interrumpir todos los subprocesos que no estén ejecutando tareas.
Es posible que la tarea ShutdownNow no se complete
- Establecer el estado del grupo de subprocesos en DETENER
- Intente detener todos los subprocesos que estén ejecutando o suspendiendo tareas
- 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
ScheduledThreadPoolExecutor
ThreadPoolExecutor
Proporciona funciones de ejecución programadas basadas en
Tiene dos métodos de cronometraje.
scheduleAtFixedRate
Tomando 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.
scheduledWithFixedDelay
Tomando 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 period
es 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 Runnable
y aquellas con valores de retorno.Callable
En realidad, el ejecutor solo procesa Runnable
tareas y Callable
las encapsula en ejecución FutureTask
adaptativa .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, Callable
capturar obtener o heredar el grupo de subprocesos para afterExecutor
registrar 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
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¡Este artículo es publicado por OpenWrite, un blog que publica varios artículos !