Exploración de la cola LinkedBlockingQueue de la programación concurrente de Java

¡Acostúmbrate a escribir juntos! Este es el día 11 de mi participación en el "Nuevo plan diario de Nuggets · Desafío de actualización de abril", haga clic para ver los detalles del evento .

En el artículo anterior, presentamos la cola sin bloqueo ConcurrentLinedQueue implementada con el algoritmo CAS. Ahora, presentemos la cola de bloqueo LinkedBlockingQueue implementada con bloqueos exclusivos.

LinkedBlockingQueue también se implementa mediante una lista de enlace único. También tiene dos nodos, que se utilizan para almacenar los nodos de cabeza y cola respectivamente, y una variable atómica con un valor inicial de 0, que se utiliza para registrar el número de elementos de la cola. . También hay dos instancias de ReentrantLock, que se utilizan para controlar la atomicidad de la entrada de elementos y la eliminación de la cola, respectivamente. El control solo puede tener elementos al mismo tiempo. Un subproceso puede adquirir el bloqueo, agregar un elemento al final de la cola y otros subprocesos tienen que esperar. Además, notEmpty y notFull son variables de condición, y tienen una cola de condición dentro de ellas para almacenar los hilos bloqueados al entrar y salir de la cola, de hecho, este es el modelo productor-consumidor. El siguiente es el código de creación del candado exclusivo.

private final AtomicInteger count = new AtomicInteger();

/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();

/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();

/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();

/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
复制代码
  • Cuando el subproceso que realiza la llamada realiza operaciones de toma, sondeo y otras operaciones en la instancia de LinkedBlockingQueue, debe adquirir el bloqueo takeLock, lo que garantiza que solo un subproceso pueda operar el nodo principal de la lista vinculada al mismo tiempo. Además, dado que el mantenimiento de la cola de condiciones dentro de la variable de condición notEmpty utiliza el mecanismo de administración de estado de bloqueo de takeLock, el subproceso de llamada primero debe adquirir el bloqueo de takeLock antes de llamar a los métodos de espera y señal de notEmpty, de lo contrario, se lanzará una IllegalMonitorStateException. Una cola de condiciones se mantiene dentro de notEmpty.Cuando el subproceso adquiere el bloqueo takeLock y llama al método de espera de notEmpty, el subproceso que llama se bloqueará y luego se colocará en la cola de condiciones dentro de notEmpty para esperar hasta que un subproceso llame a notEmpty. el método de la señal

  • 在LinkedBlockingQueue实例上执行put、offer等操作时需要获取到putLock锁,从而保证同时只有一个线程可以操作链表尾节点。同样由于条件变量 notFull 内部的条件队列的维护使用的是putLock的锁状态管理机制,所以在调用 notFull 的 await 和 signal 方法前调用线程必须先获取到putLock锁,否则会抛出 IllegalMonitorStateException 异常。notFull 内部则维护着一个条件队列,当线程获取到 putLock 锁后调用notFull的await 方法时,调用线程会被阻塞,然后该线程会被放到notFull 内部的条件队列进行等待,直到有线程调用了 notFull 的 signal 方法。如下是LinkedBlockingQueue 的无参构造函数的代码。

如下是LinkedBlockingQueue的无参构造代码

public static final int MAX_VALUE = 0x7fffffff;
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}


public LinkedBlockingQueue(int capacity) {
    if (capacity <= 0) throw new IllegalAgrumentException();
    this.capacity = capacity;
    last = head = new Node<E>(null);
}
复制代码

由该代码可知,默认队列容量为0x7fffffff,用户也可以自己指定容量,所以从一定程度上可以说LinkedBlockingQueue是有界阻塞队列。

offer操作

public boolean offer(E e) {
//(1)
    if (e == null) throw new NullPointerException();
    //(2)
    final AtomicInteger count = this.count;
    if (count.get() == capacity)
        return false;
        //(3)
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    putLock.lock();
    try {
    //(4)
        if (count.get() < capacity) {
            enqueue(node);
            c = count.getAndIncrement();
            //(5)
            if (c + 1 < capacity)
                notFull.signal();
        }
    } finally {
    //(6)
        putLock.unlock();
    }
    //(7)
    if (c == 0)
        signalNotEmpty();
        //(8)
    return c >= 0;
}
复制代码

代码(2)判断如果当前队列已满则丢弃当前元素并返回false

代码(3)获取到 putLock 锁,当前线程获取到该锁后,则其他调用put和 offer操的线程将会被阻塞(阻塞的线程被放到putLock锁的AQS阻塞队列)。

代码(4)这里重新判断当前队列是否满,这是因为在执行代码(2)和获取到 putLock 锁期间可能其他线程通过 put 或者offer 操作向队列里面添加了新元素。重新判斯队列确实不满则新元素入队,并递增计数器。

代码(5)判断如果新元素入队后队列还有空闲空间,则唤醒notFull的条件队列里面因为调用了notFull的await操作(比如执行put方法而队列满了的时候)而被阻塞的一个线程,因为队列现在有空闲所以这里可以提前唤醒一个入队线程。

El código (6) libera el bloqueo putLock adquirido.Cabe señalar aquí que la liberación del bloqueo debe hacerse finalmente porque incluso si el bloque try arroja una excepción, finalmente se ejecutará. Además, después de liberar el bloqueo, otros subprocesos bloqueados al llamar a la operación put tendrán uno para adquirir el bloqueo.

c0 en el código (7) indica que hay al menos un elemento en la cola cuando se libera el bloqueo ejecutando el código (6), y se realiza la operación signalNotEmpty si hay un elemento en la cola.

Supongo que te gusta

Origin juejin.im/post/7086457613394640926
Recomendado
Clasificación