JUC - cola de bloqueo

Tabla de contenido

Elicitación de preguntas

 1. Cola de bloqueo de un solo extremo (BlockingQueue)

2. Cola de bloqueo de doble extremo (BlockingDeque)

3. Cola de retraso (DelayQueue)


Elicitación de preguntas

Debido a la implementación del modelo consumidor-productor, cada implementación es más problemática, como el procesamiento de sincronización sincronizada o la implementación mediante bloqueos. Estos son relativamente engorrosos de implementar. Para implementar este modelo de manera simple, JUC proporciona interfaces de cola de bloqueo: BlockingQueue (cola de bloqueo de un solo extremo) y BlockingDeque (cola de bloqueo de dos extremos).

 1. Cola de bloqueo de un solo extremo (BlockingQueue)

Principio: estructura de colección procesada mediante el modo FIFO

¿Qué es FIFO?

FIFO (primero en entrar, primero en salir) es una forma común de procesar datos, también conocida como modo primero en entrar, primero en salir. En el modo FIFO, los datos que ingresan primero a la cola se procesan primero y los datos que ingresan en último lugar a la cola se procesan en último lugar.

El modelo FIFO puede entenderse como una situación de espera: por ejemplo, en la caja de un supermercado, los clientes hacen fila para pagar en orden. Después de que un cliente haya realizado el pago y se haya ido, el siguiente cliente puede comenzar a realizar el pago. Esta es la secuencia de procesamiento del modo FIFO.

En informática, el modo FIFO se utiliza a menudo en escenarios como buffers de datos, colas y algoritmos de programación. Por ejemplo, en el sistema operativo, el algoritmo de programación de procesos puede usar el modo FIFO para determinar el orden de ejecución de acuerdo con el orden en que llegan los procesos; en la comunicación de red, la cola de mensajes puede usar el modo FIFO para garantizar que se reciban los mensajes. y procesados ​​en el orden en que se envían.

Métodos comunes de cola de bloqueo de un solo extremo BlockQueue:

método describir
put(item) Coloca el elemento especificado en la cola y lo bloquea si la cola está llena hasta que haya espacio disponible.
take() Obtiene y elimina un elemento de la cola, bloqueándolo si la cola está vacía hasta que un elemento esté disponible
offer(item) Intenta colocar el elemento especificado en la cola y devuelve falso inmediatamente si la cola está llena, verdadero en caso contrario.
poll(timeout) Obtiene y elimina un elemento de la cola, devolviendo un valor nulo si la cola está vacía dentro del tiempo de espera especificado.
peek() Devuelve el primer elemento de la cola sin modificar la cola, o nulo si la cola está vacía
size() Devuelve el número actual de elementos en la cola.
isEmpty() Comprueba si la cola está vacía.
isFull() Comprueba si la cola está llena
clear() Limpiar la cola y eliminar todos los elementos.

La interfaz de cola de bloqueo de un solo extremo BlockingQueue proporciona múltiples subclases: ArrayBlockingQueue (estructura de matriz), LinkedBlockingQueue (cola de bloqueo de un solo extremo de lista vinculada), PriorityBlockingQueue (cola de bloqueo de prioridad), SynchronousQueue (cola de sincronización)

ArrayBloqueoCola

Código de caso:

El código anterior se modifica de la siguiente manera.

package Example2129;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args){
//        创建对象和资源量
        BlockingQueue<String> queue  = new ArrayBlockingQueue<String>(2);
// 创建一个包含各种美食的String数组
        String[] foods = {"披萨","汉堡", "寿司", "墨西哥炸玉米卷", "牛排", "意大利面", "烤鸭", "富士山寿司", "印度咖喱", "巴西烤肉",};

        int id;
//        两个厨师
        for (int i=0;i<2;i++){
            id = i;
            new Thread(()->{
                for (int j=0;j<10;j++){
                    try {
//                        模拟做菜时间
                        TimeUnit.SECONDS.sleep(3);
                        System.out.println(Thread.currentThread().getName()+"已经做完菜肴"+foods[j]+"并端上座子");
                        queue.put(foods[j]);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            },"厨师"+id).start();
        }
        for (int i=0;i<10;i++){
            id = i;
            new Thread(()->{
                for (int j=0;j<2;j++){
                    try {
//                        模拟客人吃饭的时间
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println(Thread.currentThread().getName()+"享用完"+queue.take()+"这道菜");
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            },"客人"+i).start();
        }
    }
}

Nota: Aunque la cola de bloqueo resuelve el problema de los subprocesos que esperan cuando los datos están llenos, no resuelve el problema de la concurrencia de subprocesos.

Cola de bloqueo vinculada

Código de caso:

package Example2130;

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

public class javaDemo {
    public static void main(String[] args) {
//        设置容量
        BlockingQueue<String> queue = new LinkedBlockingQueue<>(2);
        Random random = new Random();
        new Thread(()->{
            while (true){
                try {
                    if (queue.size()==2){
                        System.out.println("队列已满");
                        TimeUnit.SECONDS.sleep(1);
                    }else {
                        TimeUnit.SECONDS.sleep(random.nextInt(3));
                        System.out.println("存入数据");
                        queue.put("存入数据");
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(random.nextInt(3));
                    if (queue.isEmpty()){
                        System.out.println("队列空了啊");
                        TimeUnit.SECONDS.sleep(1);
                    }else {
                        System.out.println("取出数据");
                        queue.take();
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        }).start();
    }
}

Cola de bloqueo de prioridad

package Example2131;

import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;

public class javaDemo {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new PriorityBlockingQueue<>();
        Random random = new Random();
        new Thread(()->{
            try {
                for (int i=0;i<5;i++){
                    queue.put(random.nextInt(10));
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        new Thread(()->{
            try {
                for (int i=0;i<5;i++){
                    System.out.println("取出数据"+queue.take());
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
    }
}

PriorityBlockingQueueLas características son:

  • Los elementos se ordenan según su prioridad. En el ejemplo, los números más pequeños tienen mayor prioridad.
  • La complejidad temporal de las operaciones de inserción y eliminación es O (logN), donde N es el número de elementos en la cola.

Cola síncrona

package Example2132;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args){
//        创建对象和资源量
        BlockingQueue<String> queue  = new SynchronousQueue<>();
// 创建一个包含各种美食的String数组
        String[] foods = {"披萨","汉堡", "寿司", "墨西哥炸玉米卷", "牛排", "意大利面", "烤鸭", "富士山寿司", "印度咖喱", "巴西烤肉",};

        int id;
//        两个厨师
            new Thread(()->{
                for (int j=0;j<10;j++){
                    try {
//                        模拟做菜时间
                        TimeUnit.SECONDS.sleep(3);
                        System.out.println(Thread.currentThread().getName()+"已经做完菜肴"+foods[j]+"并端上座子");
                        queue.put(foods[j]);
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            },"厨师").start();

        for (int i=0;i<10;i++){
            id = i;
            new Thread(()->{
                    try {
//                        模拟客人吃饭的时间
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println(Thread.currentThread().getName()+"享用完"+queue.take()+"这道菜");
                    }catch (Exception e){
                        e.printStackTrace();
                    }
            },"客人"+i).start();
        }
    }
}

SynchronousQueueLas características son:

  • La cola no tiene capacidad y cada operación de inserción debe esperar la operación de eliminación correspondiente, y viceversa.
  • Las operaciones de inserción y eliminación están emparejadas, es decir, la inserción de un elemento debe esperar a que se consuma.

 Diferencias entre subclases de implementación:

  1. ArrayBlockingQueue(Cola de bloqueo de estructura de matriz):

    • Una cola acotada implementada en base a arreglos con capacidad fija.
    • Hay dos estrategias opcionales: justa (FIFO) e injusta (predeterminada).
    • Se utiliza un único candado internamente para lograr la seguridad del hilo.
    • La complejidad temporal de insertar y eliminar elementos es O (1).
  2. LinkedBlockingQueue(Cola de bloqueo de un solo extremo de lista vinculada):

    • Cola opcional limitada o ilimitada basada en la implementación de la lista vinculada.
    • El valor predeterminado es ilimitado, pero se puede especificar una capacidad máxima para crear una cola limitada.
    • Se utilizan dos bloqueos internamente para lograr la seguridad de la rosca, uno para las operaciones de inserción y otro para las operaciones de extracción.
    • La complejidad temporal de insertar y eliminar elementos es O (1).
  3. PriorityBlockingQueue(Cola de bloqueo prioritario):

    • Cola de prioridad ilimitada basada en la implementación del montón.
    • Los elementos se ordenan por prioridad, que está determinada por el orden natural de los elementos o un comparador personalizado.
    • No se permite almacenar ningún nullelemento internamente.
    • La complejidad temporal de insertar y eliminar elementos es O (logN), donde N es el número de elementos en la cola.
  4. SynchronousQueue(cola de sincronización):

    • Una cola de bloqueo sin buffers para la transferencia directa de elementos entre subprocesos.
    • Cada operación de inserción debe esperar la correspondiente operación de eliminación, y viceversa.
    • La cola en sí no almacena elementos, solo se usa para la transferencia de datos entre subprocesos.
    • Las operaciones de inserción y eliminación generalmente tienen un rendimiento de alta escalabilidad.

2. Cola de bloqueo de doble extremo (BlockingDeque)

BlockingDeque, que puede implementar operaciones FIFO y FILO

¿Qué es FILO?

  1. FILO (First-In, Last-Out) es un método de procesamiento de datos, también conocido como modo último en entrar, primero en salir. En el modo FILO, los últimos datos ingresados ​​se procesarán primero y los primeros datos ingresados ​​se procesarán en último lugar.
  2. El modo FILO puede entenderse como una situación en la que se apilan elementos, como colocar libros en una estantería. Cuando colocamos un libro nuevo en el estante, se coloca encima de los libros existentes, por lo que el último libro colocado queda encima. Cuando necesitemos sacar un libro, se sacará primero el último libro colocado en la parte superior. Esto es consistente con la secuencia de procesamiento del modo FILO.
  3. En informática, el modo FILO se utiliza a menudo para operaciones en estructuras de datos de pila. La pila es una estructura de datos con reglas específicas de inserción y eliminación de datos. Los últimos datos insertados se convertirán en la parte superior de la pila y los primeros datos insertados se convertirán en la parte inferior de la pila. Cuando necesitamos acceder o eliminar datos, generalmente operamos primero con los datos en la parte superior de la pila.
  4. En resumen, el modo FILO es el modo último en entrar, primero en salir, que es una forma de mantener el orden del procesamiento de datos. De manera similar a los elementos apilados o las estructuras de datos apilados, los últimos datos ingresados ​​se procesarán primero y los primeros datos ingresados ​​se procesarán en último lugar.

Métodos comunes de BlockingDeque:

método describir
addFirst(item) Agrega el elemento especificado al comienzo de la cola, generando una excepción si la cola está llena
addLast(item) Agrega el elemento especificado al final de la cola, generando una excepción si la cola está llena
offerFirst(item) Intenta agregar el elemento especificado al comienzo de la cola, devolviendo falso inmediatamente si la cola está llena, verdadero en caso contrario.
offerLast(item) Intenta agregar el elemento especificado al final de la cola, devolviendo false inmediatamente si la cola está llena, true en caso contrario
putFirst(item) Coloca el elemento especificado al comienzo de la cola, bloqueándolo si la cola está llena hasta que haya espacio disponible.
putLast(item) Coloca el elemento especificado al final de la cola, bloqueándolo si la cola está llena hasta que haya espacio disponible.
pollFirst(timeout) Obtiene y elimina un elemento del comienzo de la cola y devuelve un valor nulo si la cola está vacía dentro del tiempo de espera especificado.
pollLast(timeout) Obtiene y elimina un elemento del final de la cola y devuelve un valor nulo si la cola está vacía dentro del tiempo de espera especificado.
takeFirst() Obtiene y elimina un elemento desde el comienzo de la cola, bloqueándolo si la cola está vacía hasta que un elemento esté disponible.
takeLast() Obtiene y elimina un elemento del final de la cola, bloqueándolo si la cola está vacía hasta que un elemento esté disponible.
getFirst() Devuelve el primer elemento de la cola sin modificar la cola, o genera una excepción si la cola está vacía
getLast() Devuelve el último elemento de la cola sin modificar la cola, o genera una excepción si la cola está vacía
peekFirst() Devuelve el primer elemento de la cola sin modificar la cola, o nulo si la cola está vacía
peekLast() Devuelve el último elemento de la cola sin modificar la cola, o nulo si la cola está vacía
size() Devuelve el número actual de elementos en el deque.
isEmpty() Comprobar si deque está vacío
clear() Limpiar el deque, eliminando todos los elementos.

La cola de bloqueo de doble extremo tiene solo una subclase implementada, LinkedBlockingDeque.

Código de caso:

package Example2133;

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

public class javaDemo {
    public static void main(String[] args) {
        BlockingDeque<Integer> deque = new LinkedBlockingDeque<>();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    deque.putFirst(i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
        new Thread(()->{
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(deque.takeLast());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (deque.isEmpty()){
                    System.out.println("队列空了啦");
                    break;
                }
            }
        }).start();
    }
}

Puede ver que en casos de dos extremos, puede colocar los datos al principio o al final, y también puede obtener el principio y el final.


3. Cola de retraso (DelayQueue)

DelayQueue, una cola que muestra automáticamente el retraso de datos, se proporciona en JUC. Esta clase pertenece a la subclase de implementación de BlockingQueue. Si crea un objeto de clase y lo inserta en la cola de retraso, la clase debe heredar Delayed y anular los métodos compareTo() y getDelay().

principio:

  1. Cálculo del tiempo de retraso: cada elemento implementa la interfaz retrasada, que define un método getDelay (unidad TimeUnit), que se utiliza para calcular cuánto tiempo está lejos el elemento actual del tiempo de retraso. Este método devuelve un valor de tiempo de tipo largo, que representa el tiempo de retraso en la unidad de tiempo.

  2. Almacenamiento en cola: una cola de prioridad ordenada (PriorityQueue) se utiliza internamente para almacenar elementos. Los elementos se ordenarán según su latencia, es decir, el elemento con menor latencia estará al principio de la cola.

  3. Adición de elementos: llame al método de oferta (E e) para agregar un elemento a la cola. Cuando se inserta un elemento, su posición se determina en función de su tiempo de retardo.

  4. Recuperación de elementos: llame al método take() para eliminar elementos de la cola que llegan después de un retraso. Si la cola está vacía, el hilo se bloquea y espera hasta que se pueda recuperar un elemento.

  5. Sincronización de adición y eliminación: sincronice las operaciones de adición y eliminación de la cola para garantizar la seguridad en un entorno de subprocesos múltiples.

  6. Eliminación programada: una vez que el tiempo de almacenamiento de un elemento en la cola exceda su tiempo de retraso, se eliminará automáticamente.

Métodos comúnmente utilizados:

nombre del método describir
enqueue(item, delay) Pone en cola lo especificado itemy delaylo ejecuta después de milisegundos.
dequeue() Retire la cola y devuelva la tarea diferida más antigua.
getDelay(item) Devuelve itemel tiempo de retraso restante en milisegundos para el valor especificado, o itemun número negativo si ha expirado.
remove(item) 从队列中移除指定的 item
size() 返回队列中延迟任务的数量。
isEmpty() 判断队列是否为空。
clear() 清空队列,移除所有的延迟任务。
getExpiredItems(now) 返回所有已过期的任务,并从队列中移除它们。
getNextExpiringItem() 返回下一个即将过期的任务,但不从队列中移除它。

 案例代码:

package Example2134;

import org.jetbrains.annotations.NotNull;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

class Student implements Delayed {
    private String name;
//    设置停留时间
    private  long delay;
//    设置离开时间
    private long expire;
    Student(String name, long delay , TimeUnit unit){
    this.name=name;
    this.delay = TimeUnit.MILLISECONDS.convert(delay,unit);
    this.expire = System.currentTimeMillis()+this.delay;
    }

    @Override
    public String toString() {
        return this.name+"同学已经到达预计停留的时间"+TimeUnit.SECONDS.convert(this.delay,TimeUnit.MILLISECONDS)+"秒,已经离开了";
    }

//    延迟时间计算
    @Override
    public long getDelay(@NotNull TimeUnit unit) {
        return unit.convert(this.expire - System.currentTimeMillis(),TimeUnit.MILLISECONDS);
    }

//    队列弹出计算
    @Override
    public int compareTo(@NotNull Delayed o) {
        return (int) (this.delay-this.getDelay(TimeUnit.MILLISECONDS));
    }
}

public class javaDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Student> students = new DelayQueue<Student>();
        students.put(new Student("黄小龙",3,TimeUnit.SECONDS));
        students.put(new Student("张三",1,TimeUnit.SECONDS));
        students.put(new Student("李四",5,TimeUnit.SECONDS));
        while (!students.isEmpty()){
            Student stu = students.take();
            System.out.println(stu);
            TimeUnit.SECONDS.sleep(1);
        }
    }
}


Supongo que te gusta

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