[JavaEE Elementary] Subprocesos múltiples (4) Grupo de subprocesos del temporizador de cola de bloqueo

inserte la descripción de la imagen aquí

Caja de múltiples hilos

cola de bloqueo

concepto

La cola de bloqueo es una cola especial. También obedece el principio de " primero en entrar, primero en salir ".
La cola de bloqueo puede ser una estructura de datos segura para subprocesos y tiene las siguientes características:

  • Cuando la cola está llena, continuar ingresando a la cola se bloqueará hasta que otros subprocesos tomen elementos de la cola.
  • Cuando la cola está vacía, continuar saliendo de la cola también se bloqueará hasta que otros subprocesos inserten elementos en la cola.

Un escenario de aplicación típico de cola de bloqueo es el " modelo de consumidor productor " Este es un modelo de desarrollo muy típico.

productor consumidor modelo

El modelo productor-consumidor utiliza un contenedor para resolver el problema del fuerte acoplamiento entre productores y consumidores.
Los productores y los consumidores no se comunican directamente entre sí, sino que se comunican a través de colas de bloqueo, por lo que los productores no tienen que esperar después de producir los datos. Después de ser procesado por el consumidor, se envía directamente a la cola de bloqueo, y el consumidor no solicita datos al productor, sino que los toma directamente de la cola de bloqueo.

  1. La cola de bloqueo es equivalente a un búfer, que equilibra el poder de procesamiento de productores y consumidores.
  2. El bloqueo de colas también desvincula a productores y consumidores.

Cola de mensajes: una cola especial, que equivale a agregar un "tipo de mensaje" sobre la base de la cola de bloqueo. Primero en entrar, primero en salir según la categoría especificada.
inserte la descripción de la imagen aquí
En el escenario anterior: en este momento, A reenvía la solicitud a B y B devuelve el resultado a A después del procesamiento. En este punto, se puede considerar como A llamando a B. En este momento, el acoplamiento entre A y B es relativamente alto. Si hay un problema con B, entonces puede haber un problema con A. Si se agrega otro servidor C en este momento, se debe volver a modificar el código de A. En el proceso, es probable que ocurran errores.
Para los escenarios anteriores, el uso del modelo productor-consumidor puede reducir efectivamente el acoplamiento.
inserte la descripción de la imagen aquí

En este punto, el acoplamiento entre A y B se reduce considerablemente.
A no conoce a B, A solo conoce la cola. De la misma manera, B no conoce a A y B solo conoce la cola. Si alguno de AB tiene un error, el impacto en el otro es muy pequeño.
Escribamos un modelo de consumidor productor:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ThreadDemo22 {
    
    
    public static void main(String[] args) {
    
    
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();

        //创建两个线程,来作为生产者和消费者
        Thread customer = new Thread(()->{
    
    
            while(true){
    
    
                try {
    
    
                    Integer result = blockingQueue.take();
                    System.out.println("消费元素:"+result);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
            }
        });
        customer.start();
        Thread producer = new Thread(()->{
    
    
            int count = 0;
            while (true){
    
    
                try {
    
    
                    blockingQueue.put(count);
                    System.out.println("生产元素:"+ count);
                    count++;
                    //控制500ms生产一个元素
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
            }
        });
        producer.start();
    }
}

Bloqueo de colas en la biblioteca estándar

public interface BlockingQueue<E> extends Queue<E> {
    
    ...};

inserte la descripción de la imagen aquí

BlockingQueue es una interfaz. La clase de implementación real es LinkedBlockingQueue.
El método put se usa para bloquear la entrada a la cola y el método take se usa para bloquear la salida de la cola.
BlockingQueue también tiene métodos como oferta, encuesta, vistazo, etc., pero estos los métodos no tienen características de bloqueo

inserte la descripción de la imagen aquí

Implemente una cola de bloqueo usted mismo

import java.util.concurrent.BlockingQueue;

//自己实现一个阻塞队列:
class MyBlockingQueue{
    
    
    private int[] items = new int[1000];
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    //入队列
    public void put(int value) throws InterruptedException {
    
    
        synchronized (this) {
    
    
            while (size == items.length) {
    
    
                //队列满了,此时要产生阻塞
                this.wait();
            }
            items[tail] = value;
            tail++;
            if (tail >= items.length) {
    
    
                tail = 0;
            }
            size++;
            //唤醒 take 中的wait
            this.notify();
        }
    }
    //出队列
    public Integer take() throws InterruptedException {
    
    
        int result = 0;
        synchronized (this){
    
    
            while(size == 0){
    
    
                this.wait();
            }
            result = items[head];
            head++;
            if(head >= items.length){
    
    
                head = 0;
            }
            size--;
            //唤醒 put中的wait
            this.notify();
        }
        return result;
    }
}
public class ThreadDemo23 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        /*MyBlockingQueue queue = new MyBlockingQueue();
        queue.put(1);
        queue.put(2);
        queue.put(3);
        queue.put(4);
        int result = 0;
        result = queue.take();
        System.out.println("result = " + result);
        result = queue.take();
       System.out.println("result = " + result);
        result = queue.take();
        System.out.println("result = " + result);
        result = queue.take();
        System.out.println("result = " + result);*/


        MyBlockingQueue queue = new MyBlockingQueue();
        Thread customer = new Thread(()->{
    
    
            while(true){
    
    
                try {
    
    
                    Integer result = queue.take();
                    System.out.println("消费元素:"+result);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
            }
        });
        customer.start();

        Thread produce = new Thread(()->{
    
    
            int count = 0;
            while(true){
    
    
                System.out.println("生产元素:"+count);
                try {
    
    
                    queue.put(count);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
                count++;
            }
        });
        produce.start();
    }

}

inserte la descripción de la imagen aquí

Temporizador

concepto

Temporizador: ejecuta un método/código preparado previamente después de un período de tiempo específico.
Los temporizadores son un componente importante en el desarrollo de software. Especialmente cuando se trata de programación en red. Similar a un "despertador". Después de que se alcanza un tiempo establecido, se ejecuta un código específico.

Temporizadores en la biblioteca estándar

Se proporciona una clase de temporizador en la biblioteca estándar. El método principal de la clase de temporizador es el programa.
El programa contiene dos parámetros. El primer parámetro especifica el código de tarea que se ejecutará y el segundo parámetro especifica cuánto tiempo llevará ejecutar ( en milisegundos).

Timer timer = new Timer();
        timer.schedule(new TimerTask() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("运行定时器任务1!");
            }
        },3000);
timer.schedule();

El efecto de este método es registrar una tarea para el temporizador, la tarea no se ejecutará inmediatamente, sino que se ejecutará a la hora especificada.

Implementar un temporizador

  1. Deje que las tareas registradas se ejecuten dentro de un tiempo específico.Método
    : Cree un subproceso dentro del temporizador por separado, y deje que este subproceso escanee **(escanear subproceso)** periódicamente para determinar si la tarea está cronometrada.
  2. Un temporizador es un **(hilo de programación)** que puede registrar N tareas, y las N tareas se ejecutarán en orden de acuerdo con el tiempo originalmente acordado.

Estas N tareas deben guardarse mediante una cola de bloqueo de prioridad . (La prioridad se debe a que la prioridad se puede establecer de acuerdo con el tiempo pequeño. En este momento, el primer elemento de la cola es la primera tarea que se ejecutará en toda la cola. En este momento, el subproceso de escaneo mencionado anteriormente solo necesita verifique el primer elemento de la cola. , no es necesario atravesar toda la cola. (La eficiencia ha mejorado mucho))

在这里插入代码片import java.util.concurrent.PriorityBlockingQueue;

//使用这个类表示一个定时器中的任务
class MyTask implements Comparable<MyTask>{
    
    
    //要执行的任务内容
    private Runnable runnable;

    //任务在何时执行
    private long time;

    public MyTask(Runnable runnable,long time){
    
    
        this.runnable = runnable;
        this.time = time;
    }

    //获取当前任务的时间
    public long getTime() {
    
    
        return time;
    }

    //执行任务
    public void run(){
    
    
        runnable.run();
    }

    @Override
    public int compareTo(MyTask o) {
    
    
        //重写方法,按照时间排序。队首元素是时间最小的任务。
        return (int)(this.time-o.time);
    }
}
//定时器
class MyTimer{
    
    
    //扫描线程
    private Thread t = null;

    //使用一个优先级队列。来保存任务
    private PriorityBlockingQueue<MyTask> queue = new PriorityBlockingQueue<>();

    //使用这个对象来进行加锁/等待通知
    private Object locker = new Object();

    public MyTimer(){
    
    
        t = new Thread(){
    
    
            public void run() {
    
    
                while (true) {
    
    
                    try {
    
    
                        //取出队首元素,检查看看队首元素任务是否时间到了
                        //如果时间没到,就把任务塞回队列中
                        //如果时间到了,就把任务进行执行
                        synchronized (locker) {
    
    
                            MyTask myTask = queue.take();
                            long curTime = System.currentTimeMillis();
                            if (curTime < myTask.getTime()) {
    
    
                                //时间没到,塞回队列
                                queue.put(myTask);
                                //在put之后,进行一个wait
                                locker.wait(myTask.getTime() - curTime);
                            } else {
    
    
                                //时间到了,执行任务
                                myTask.run();
                            }
                        }
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
    }
    public void schedule(Runnable runnable,long after){
    
    
       MyTask task = new MyTask(runnable,System.currentTimeMillis()+after);
       queue.put(task);
        synchronized (locker) {
    
    
            locker.notify();
        }
    }

}
public class ThreadDemo25 {
    
    
    public static void main(String[] args) {
    
    
        MyTimer myTimer = new MyTimer();
        myTimer.schedule(new Runnable(){
    
    
            public void run(){
    
    
                System.out.println("任务1");
            }
        },2000);
        myTimer.schedule(new Runnable(){
    
    
            public void run(){
    
    
                System.out.println("任务2");
            }
        },1000);
    }
}

resultado de la operación:
inserte la descripción de la imagen aquí

Grupo de subprocesos

La importancia de la existencia del grupo de subprocesos: utilice el proceso para lograr la programación concurrente. muy pesado. En este momento, se introducen subprocesos, y los subprocesos también se denominan "procesos ligeros". Crear subprocesos es más eficiente que crear procesos; destruir subprocesos es más eficiente que destruir procesos; programar subprocesos es más eficiente que programar procesos. En este punto, el uso de subprocesos múltiples puede reemplazar procesos en muchos casos para lograr una programación concurrente. Pero a medida que aumenta el grado de concurrencia, aumentan nuestros requisitos de rendimiento. Los hilos también se han vuelto menos ligeros.
Cuando queremos mejorar aún más la eficiencia , hay dos formas:

  1. Hilo ligero - corutina/fibra
  2. Utilice un grupo de subprocesos para reducir la sobrecarga de creación/destrucción de subprocesos.

Es decir, cree los subprocesos que deben usarse con anticipación y colóquelos en el grupo. Cuando necesite usarlo más tarde, consígalo directamente de la piscina. Cuando termines de usarlo, colócalo en la piscina.

Las operaciones anteriores son más eficientes que la creación/destrucción.
El kernel del sistema operativo realiza la creación/destrucción de subprocesos. Obtener del grupo y devolverlo al grupo se puede lograr mediante nuestro propio código, sin tener que entregarlo al kernel para que funcione.

El grupo de subprocesos en la biblioteca estándar

ExecutorService pool = Executors.newFixedThreadPool(10);

inserte la descripción de la imagen aquí
Las operaciones anteriores utilizan un método estático de una clase para construir directamente un objeto. (Ocultar nuevo)
Este método se denomina "método de fábrica".
La clase que proporciona este método de fábrica se llama "clase de fábrica." Este código aquí usa el patrón de diseño del patrón de fábrica .

patrón de fábrica

Patrón de fábrica: use métodos ordinarios en lugar de constructores para crear objetos.
Por ejemplo:
ahora hay una clase:

class Point{
    
    
	public Point(double X,double Y){
    
    };
	public Point(double R,double A){
    
    };
}

En este punto, el código sale mal porque solo puede haber un constructor. Pero queremos implementar dos formas de representar tablas. Entonces, en este caso, podemos usar el patrón de fábrica.

class PointFactory{
    
    
	public static Point makePointXY(double X,double Y){
    
    };
	public static Point makePointRA(double R,double A){
    
    };
}

En este momento, el problema mencionado anteriormente está resuelto.

El método proporcionado en el grupo de subprocesos: enviar;

public static void main(String[] args) {
    
    
        ExecutorService pool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 1000; i++) {
    
    
            int n = i;
            pool.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println("hello"+n);
                }
            });
        }

    }

A lo que debemos prestar atención es al problema de captura de variables en el código anterior . ¿Es por eso que una variable n se crea por separado al imprimir, en lugar de i?
i es una variable local en el hilo principal (en la pila del hilo principal). A medida que el subproceso principal ejecuta este bloque de código, se destruye. Para evitar la diferencia de alcance, se ha destruido i cuando se ejecuta la ejecución posterior. Entonces hay una captura de variables, es decir, deje que el método de ejecución copie la i del hilo principal en este momento a la pila de ejecución. La captura de variables solo puede capturar variables modificadas finales (antes de JDK1.8).Después de JDK1.8, el estándar se relaja, siempre que la variable no se modifique en el código, también se puede capturar.

En el código anterior, i se modifica y no se puede capturar. Y n no se modifica, aunque no hay modificación final, también se puede capturar.

inserte la descripción de la imagen aquí

ThreadPoolExecutor(); Explicación detallada de los parámetros del método de construcción (énfasis)

inserte la descripción de la imagen aquí
Para facilitar la comprensión de la relación entre los parámetros aquí, usamos ejemplos en la vida para entender por analogía, suponiendo que aquí hay una empresa:

  • corePoolSize indica el número de subprocesos principales, los empleados oficiales de la empresa.

¿Cuál es el número más adecuado de subprocesos centrales? Suponiendo que la CPU tiene N núcleos, ¿cuál es el número más adecuado de subprocesos centrales? ¿Es 2N? ¿Es 1.5N? Siempre que pueda nombrar un número específico, es incorrecto , el más
adecuado El número de subprocesos principales depende de la situación y los escenarios comerciales, y no existe un valor estándar absoluto.

  • MaximumPoolSize indica el número máximo de subprocesos, que es la suma del número de subprocesos centrales y el número de subprocesos no centrales. Los empleados formales de la empresa y los trabajadores de cero horas contratados (subprocesos no centrales) reclutarán cero cuando el trabajo
    existente no se puede completar Los trabajadores del tiempo ayudan con el trabajo.
  • keepAliveTime El tiempo más largo para que un subproceso no central espere una nueva tarea. Después de este tiempo, el subproceso se destruirá; es equivalente al tiempo de pesca más largo para los trabajadores de cero horas. La empresa no admite inactivos y
    cero -Los trabajadores por horas no tienen trabajo durante mucho tiempo Si no lo hace, será despedido La estrategia general es garantizar la garantía mínima para los empleados regulares y el ajuste dinámico para los trabajadores temporales.
  • unidad La unidad de tiempo del parámetro anterior.
  • La cola de tareas (cola de bloqueo) del grupo de subprocesos de workQueue, registra tareas en la cola a través del método de envío.
  • threadFactory fábrica de hilos, esquema de creación de hilos.
  • La estrategia de rechazo del controlador describe cómo lidiar con la adición de tareas cuando la cola de tareas del grupo de subprocesos está llena.

En la biblioteca estándar de Java se proporcionan cuatro estrategias de rechazo, de la siguiente manera:

Modificador y Tipo Clase y descripción
clase estática ThreadPoolExecutor.AbortPolicy Si hay demasiadas tareas y la cola está llena, se lanzará una excepción RejectedExecutionException directamente.
clase estática ThreadPoolExecutor.CallerRunsPolicy Si hay demasiadas tareas, la cola está llena y se agregan las tareas adicionales, quién es el responsable de ejecutarlas.
clase estática ThreadPoolExecutor.DiscardOldestPolicy Si hay demasiadas tareas y la cola está llena, descarte las tareas sin procesar más antiguas.
clase estática ThreadPoolExecutor.DiscardPolicy Si hay demasiadas tareas y la cola está llena, descarte las tareas adicionales.

Implementar grupo de subprocesos

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class MyThreadPool{
    
    
    private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();

    //n表示线程的数量
    public MyThreadPool(int n){
    
    
        //创建线程
        for (int i = 0; i < n; i++) {
    
    
            Thread t = new Thread(()->{
    
    
                while(true){
    
    
                    try {
    
    
                        Runnable runnable = queue.take();
                        runnable.run();
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
            });
            t.start();
        }
    }
    //
    public void submit(Runnable runnable) {
    
    
        try {
    
    
            queue.put(runnable);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}
public class ThreadDemo27 {
    
    
    public static void main(String[] args) {
    
    
        MyThreadPool pool = new MyThreadPool(10);
        for (int i = 0; i < 1000; i++) {
    
    
            int n = i;
            pool.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println("hello " + n);
                }
            });
        }
    }
}

inserte la descripción de la imagen aquí
inserte la descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_61138087/article/details/130366186
Recomendado
Clasificación