JUC - grupo de subprocesos

Tabla de contenido

1. Introducción al grupo de subprocesos

2. Creación de un grupo de subprocesos.

 3. Grupo de subprocesos especiales

3.1.Procesamiento asincrónico de CompletionService

3.2.ThreadPoolEjecutor

 3.3 Grupo de unión de horquillas


        Aunque la tecnología de subprocesos múltiples ayuda enormemente a la eficiencia de la operación del programa, la sobrecarga del sistema será enorme cuando se crean y destruyen demasiados subprocesos. Entonces, para lograr la capacidad de administración de subprocesos y reducir la sobrecarga. JUC proporciona el concepto de grupo de subprocesos y métodos de implementación relacionados.

1. Introducción al grupo de subprocesos

Pregunta de la entrevista: Implementación del grupo de subprocesos

Cree una cola de bloqueo para acomodar la tarea, cree suficientes subprocesos cuando ejecute la tarea por primera vez y procese la tarea. Después de eso, cada subproceso de trabajo obtendrá automáticamente subprocesos de la cola de tareas hasta que la tarea en la cola de tareas sea 0, en En qué momento el hilo En el estado de espera, una vez que se agrega una tarea de trabajo a la cola de tareas, el hilo de trabajo se despierta inmediatamente para su procesamiento para lograr la reutilización del hilo.

Cómo crear cuatro grupos de subprocesos

nombre del método describir
newCachedThreadPool() Cree un grupo de subprocesos que ajuste automáticamente la cantidad de subprocesos según sea necesario.
newFixedThreadPool(int nThreads) Cree un grupo de subprocesos con un número fijo de subprocesos.
newSingleThreadScheduledExecutor() Cree un grupo de subprocesos que contenga solo un subproceso para programar la ejecución de tareas de acuerdo con un cronograma.
newScheduledThreadPool(int corePoolSize) Cree un grupo de subprocesos con una cantidad fija de subprocesos principales que puedan programar la ejecución de tareas de acuerdo con un cronograma y tenga una cierta cantidad de grupos de subprocesos de tamaño variable.

Las funciones de cuatro grupos de subprocesos.

  • newCachedThreadPool(): cree un grupo de subprocesos que ajuste automáticamente la cantidad de subprocesos según sea necesario. Es adecuado para escenarios donde se ejecuta una gran cantidad de tareas asincrónicas a corto plazo. El grupo de subprocesos aumentará o disminuirá automáticamente la cantidad de subprocesos según la cantidad de tareas.

  • newFixedThreadPool(int nThreads): crea un grupo de subprocesos con un número fijo de subprocesos. Es adecuado para escenarios donde es necesario controlar la cantidad de subprocesos simultáneos y la cantidad de subprocesos es fija.

  • newSingleThreadScheduledExecutor(): Cree un grupo de subprocesos que contenga un solo subproceso y la ejecución de las tareas se puede organizar de acuerdo con el plan. Adecuado para escenarios donde las tareas deben ejecutarse en secuencia, cada tarea se ejecutará después de que se complete la tarea anterior.

  • newScheduledThreadPool(int corePoolSize): Cree un grupo de subprocesos con una cantidad fija de subprocesos principales, que pueda programar la ejecución de tareas de acuerdo con un plan y tenga una cierta cantidad de grupos de subprocesos de tamaño variable. Es adecuado para escenarios donde el tiempo de ejecución de la tarea debe organizarse de acuerdo con el plan. El número de subprocesos principales es fijo, pero se pueden agregar subprocesos adicionales según sea necesario para manejar más tareas.

Crear grupo de subprocesos

El grupo de subprocesos creado por la clase Executor se describe principalmente a través de dos tipos de interfaces: ExecutorService (grupo de subprocesos) y ScheduledExecutorService (grupo de subprocesos de programación).

Métodos comúnmente utilizados en grupos de subprocesos.

nombre del método describir
execute(Runnable command) Ejecute los comandos dados en secuencia y envíelos al grupo de subprocesos para su ejecución.
submit(Runnable task) Envía una tarea ejecutable al grupo de subprocesos y devuelve un Futuro que representa los resultados pendientes de la tarea.
submit(Callable task) Envía una tarea invocable al grupo de subprocesos y devuelve un Futuro que representa los resultados pendientes de la tarea.
invokeAll(Collection<? extends Callable<T>> tasks) Ejecute y espere a que se complete cada tarea en orden antes de que se completen todas las tareas en la lista de tareas dada y devuelva una lista futura que represente los resultados de todas las tareas.
invokeAny(Collection<? extends Callable<T>> tasks) Ejecuta la lista de tareas dada, devuelve los resultados de una de las tareas que se completó con éxito y cancela todas las demás tareas.
isShutdown() Si el grupo de subprocesos ya ha llamado shutdown()al método o shutdownNow(), devuelva true. En caso contrario regresar false.
shutdown() Cierra elegantemente el grupo de subprocesos, deja de aceptar nuevas tareas e intenta reanudar la ejecución de cualquier tarea no finalizada.
schedule() Programa una tarea para que se ejecute después de un retraso determinado y devuelve una representación de los resultados pendientes de la tarea ScheduledFuture.

2. Creación de un grupo de subprocesos.

Pregunta de la entrevista: ¿Cómo se crean los subprocesos en el grupo de subprocesos?

Creación de cuatro objetos de hilo.

1. Código de caso: cree un grupo de subprocesos de caché (se establece un grupo de subprocesos y el número de subprocesos no está limitado (por supuesto, no puede exceder el valor máximo de Integer). Cuando se agrega una nueva tarea, se creará un subproceso procesado, o se reutilizará el hilo previamente inactivo (o se inicia un hilo nuevamente, pero una vez que un hilo ha estado esperando durante 60 segundos (es decir, no hay nada que hacer durante un minuto), se terminará)

package Example2135;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class javaDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
//        如果线程池中的线程不够用,则会自动新创建线程,线程最多为Integer.MAX_VALUE个
        for (int i=0;i<100;i++){
            executorService.submit(()->{
                System.out.println(Thread.currentThread().getId()+"--"+Thread.currentThread().getName());
            });
        }
        if (!executorService.isShutdown()){
            executorService.shutdown();
        }
    }
}

Nota: con respecto a la relación entre los métodos de ejecución y envío

  1. Tipo de valor de retorno: execute()el método no tiene valor de retorno y solo se usa para enviar tareas ejecutables; mientras que submit()el método encapsula la tarea enviada en un Futureobjeto a través del cual se pueden obtener los resultados de la ejecución de la tarea.

  2. Tipo de parámetro: execute()el método acepta un Runnableparámetro de tipo, que se utiliza para enviar una tarea que no necesita devolver un resultado; y submit()el método puede aceptar Runnableun Callableparámetro de tipo, utilizado para enviar una tarea que puede o no devolver un resultado. .

  3. Manejo de excepciones: execute()el método no puede capturar excepciones durante la ejecución de la tarea. Si la tarea genera una excepción, el grupo de subprocesos no puede detectarla, pero submit()el método puede capturar excepciones durante la ejecución de la tarea y Futuredevolver información de excepción en el formulario.

En resumen, execute()es más adecuado para realizar tareas simples que no necesitan devolver resultados, mientras que submit()es más flexible y puede usarse para realizar tareas que devuelven resultados y puede capturar excepciones de tareas.

 2. Código de caso: cree un grupo de subprocesos de longitud fija (el número máximo de subprocesos se determinó durante la inicialización. Si la capacidad de agregar tareas excede la capacidad de procesamiento de los subprocesos, se establecerá una cola de bloqueo para acomodar el exceso de tareas)

package Example2136;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class javaDemo {
    public static void main(String[] args) {
//    创建固定长度为4的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(4);
//           让线程输出自己的默认名称
        for(int i=0;i<10;i++){
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName());
            });
        }
        executorService.shutdown();
    }

}

3. Código de caso: cree un solo subproceso (como sugiere el nombre, solo hay un subproceso ejecutándose en un grupo. Este subproceso nunca se agota y, debido a que es un subproceso, cuando hay varias tareas que deben procesarse, serán colocados en una cola de bloqueo ilimitada. Procesados ​​uno por uno)

package Example2137;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class javaDemo {
    public static void main(String[] args) {
//        创建单线程对象
        ExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
        for (int i=0;i<10;i++){
            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName());
            });
        }
        executorService.shutdown();
    }
}

 4.Código de caso: crear un grupo de subprocesos de programación

package Example2139;

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args) {
//        创建调度线程池,并设置内核线程数量为1
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
//        设置调度的任务,并设置3秒后执行,之后每隔2秒执行一次
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Date date = new Date();
                System.out.println(Thread.currentThread().getName()+"为您播报:当前时间为"+ date);
            }
        },3,2, TimeUnit.SECONDS);
    }
}

Si se pasa una instancia de interfaz invocable en el grupo de subprocesos, el resultado devuelto se puede obtener a través de la interfaz Future. La interfaz ExecutorService proporciona dos métodos, invokeAny e invokeAll, para implementar la ejecución de un conjunto de instancias invocables.

Código de caso: ejecutar un conjunto de instancias invocables

package Example2140;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.*;

public class javDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
//        保存多个线程对象
        Set<Callable<String>> allThread = new HashSet<Callable<String>>();
//        集合中追加线程
        for (int i=0;i<10;i++){
            final int temp = i;
            allThread.add(()->{
               return Thread.currentThread().getName()+temp;
            });
        }
//        创建固定长度的线程
        ExecutorService service = Executors.newFixedThreadPool(3);
//        使用Future接收类型
        List<Future<String>> list = service.invokeAll(allThread);
        for (Future<String> future : list){
            System.out.println(future.get());
        }
    }
}


 3. Grupo de subprocesos especiales

3.1.Procesamiento asincrónico de CompletionService

La función principal de CompletionService es obtener los resultados devueltos por el grupo de subprocesos mediante procesamiento asincrónico. CompletionService puede recibir tareas de subprocesos implementadas por Callable o Runnable. Y se puede crear una instancia del objeto de interfaz a través de la subclase ExecutorCompletionService

¿Qué es el procesamiento asincrónico?

El procesamiento asincrónico significa que durante la ejecución de la tarea, puede continuar realizando otras operaciones sin esperar a que se complete la tarea. Mediante el procesamiento asincrónico, se puede mejorar la velocidad de respuesta y la eficiencia del programa. En el procesamiento sincrónico tradicional, debe esperar a que se complete una tarea antes de ejecutar la siguiente, mientras que el procesamiento asincrónico puede ejecutar varias tareas al mismo tiempo, lo que mejora la concurrencia de tareas.

Caso: utilice la interfaz CompletionService para obtener resultados de tareas de ejecución asincrónicas

package Example2141;

import java.util.concurrent.*;

//线程体
class ThreadItem implements Callable<String> {
    @Override
    public String call() throws Exception {
        //        获取当前时间戳
        long TimeMillis = System.currentTimeMillis();
        try {
            System.out.println("[start]"+Thread.currentThread().getName());
            TimeUnit.SECONDS.sleep(3);
            System.out.println("[end]"+Thread.currentThread().getName());
        }catch (Exception e){}
        return Thread.currentThread().getName()+":"+ TimeMillis;
    }
}

public class javaDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        CompletionService<String> completionService = new ExecutorCompletionService<String>(executorService);
//        信息生产者
        for (int i=0;i<10;i++){
//            提交线程
            completionService.submit(new ThreadItem());
        }
        for (int i=0;i<10;i++){
            System.out.println("获取数据"+completionService.take().get());
        }
//        关闭线程池
        executorService.shutdown();
    }
}

3.2.ThreadPoolEjecutor

Los grupos de subprocesos se pueden crear a través de la clase Executors, y la creación a través de la clase Executors se basa en la implementación de la clase ThreadPoolExecutor. En algunas circunstancias especiales, los desarrolladores también pueden usar directamente la clase ThreadPoolExecutor para implementar su propio grupo de subprocesos combinando colas de bloqueo y estrategias de rechazo.

¿Qué es una estrategia de rechazo?

La política de rechazo es una estrategia que determina cómo manejar las tareas enviadas en el grupo de subprocesos cuando exceden la capacidad del grupo de subprocesos y no se pueden procesar. La política de rechazo se activa cuando la cola de trabajo (cola de bloqueo) en el grupo de subprocesos está llena y no hay subprocesos libres para realizar tareas.

Cuatro estrategias de rechazo

Denegar política describir
Política de aborto La estrategia predeterminada es que cuando el grupo de subprocesos no puede manejar una nueva tarea, RejectedExecutionExceptionse lanza una excepción directamente, lo que indica que se rechaza la ejecución de la tarea.
Política de ejecuciones de llamadas Cuando el grupo de subprocesos no puede manejar la nueva tarea, la tarea se devuelve a la persona que llama para su procesamiento. Es decir, el hilo que llama submital método realiza la tarea.
Descartar política más antigua Cuando el grupo de subprocesos no puede manejar nuevas tareas, descartará las primeras tareas que ingresaron a la cola de trabajos e intentará volver a enviar las tareas actuales.
Descartar política Cuando el grupo de subprocesos no puede procesar nuevas tareas, descartará silenciosamente las tareas que no se pueden procesar sin dar ningún aviso. Esta estrategia puede provocar la pérdida de tareas, así que úsela con precaución.

Código de caso:

package Example2142;

import java.util.concurrent.*;

public class javaDemo {
    public static void main(String[] args) {
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
//        通过ThreadPoolExecutor创建线程池,该线程有2个内核线程,最大线程量为2,每个线程存活时间为6秒,使用默认拒绝策略
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,2,6L, TimeUnit.SECONDS,queue, Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        for (int i=0;i<5;i++){
            threadPoolExecutor.submit(()->{
                System.out.println("[BEFORE]"+Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(2);
                }catch (Exception  e){}
                System.out.println("[AFTER]" +Thread.currentThread().getName());
            });
        }
        
    }
}

 3.3 Grupo de unión de horquillas

Para aprovechar al máximo las ventajas de rendimiento de las CPU de múltiples núcleos posteriores a JDK1.7, un cálculo comercial complejo se puede dividir y entregar a varias CPU para el cálculo en paralelo, lo que se utiliza para mejorar el rendimiento de ejecución del programa, de modo que Se introdujo la clase ForkJoinPool, que contiene dos operaciones.

Fork (operación de descomposición): divide una gran empresa en múltiples tareas pequeñas y ejecútalas en el marco

Unirse (operación de fusión): la tarea principal esperará a que se completen varias subtareas antes de fusionarlas.

Hay dos subclases en ForkJoinPool, RecursiveTask (tarea con valor de retorno) y RecursiveAction (tarea sin valor de retorno)

Métodos comúnmente utilizados de esta clase:

método ilustrar
fork() Divida la tarea en subtareas más pequeñas y ejecútelas de forma asincrónica. Coloque la tarea actual en la cola de trabajo para que otros subprocesos de trabajo la obtengan y ejecuten.
join() Espere el resultado de la ejecución de la tarea actual y devuelva el resultado. Si la tarea no se ha completado, el hilo de llamada se bloqueará y no continuará la ejecución hasta que se complete la tarea.
isCompleteNormally() Determine si la tarea se ha completado normalmente. Este método devuelve si la tarea se completa sin generar una excepción; truesi la tarea se cancela o se produce una excepción, devuelve false.
invokeAll(tasks) Ejecute el conjunto de tareas proporcionado y espere a que se completen todas las tareas. Este método divide una colección de tareas en subtareas más pequeñas que los subprocesos de trabajo ejecutan de forma asincrónica en el grupo de subprocesos.
getException() Obtenga excepciones que ocurren durante la ejecución de la tarea. Si la tarea se completa normalmente o no se ha completado, este método devuelve null; si la tarea se cancela o termina de manera anormal, se devuelve el motivo de la excepción.

Código de caso:

package Example2143;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

class SumTask extends RecursiveTask<Integer> {
    private int start;
    private int end;

    public SumTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int sum = 0;
        if (this.end - this.start < 100) {
            for (int x = this.start; x <= this.end; x++) {
                sum += x;
            }
        } else {
            int middle = (start + end) / 2;
            SumTask leftTask = new SumTask(this.start, middle);
            SumTask rightTask = new SumTask(middle + 1, this.end);
            leftTask.fork();
            rightTask.fork();
            // 等待子任务完成并将结果相加
            sum = leftTask.join() + rightTask.join();
        }
        return sum;
    }
}

public class javaDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SumTask task = new SumTask(0,100);
        ForkJoinPool pool = new ForkJoinPool();
        Future<Integer> future = pool.submit(task);
        System.out.println(future.get());
    }
}


Supongo que te gusta

Origin blog.csdn.net/dogxixi/article/details/132427199
Recomendado
Clasificación