No está permitido tener programadores de Java que no entiendan el principio de implementación de la cola de bloqueo BlockingQueue

¡Continúe creando, acelere el crecimiento! Este es el 4to día de mi participación en el "Nuggets Daily New Plan · October Update Challenge", haz clic para ver los detalles del evento

Parece que rara vez usamos BlockingQueue en nuestro desarrollo habitual. Por ejemplo, cuando queremos almacenar un conjunto de datos, usaremos ArrayList, y si queremos almacenar datos de pares clave-valor , usaremos HashMap. ¿En qué escenarios necesitamos usar BlockingQueue?

1. Escenarios de aplicación de BlockingQueue

Después de que procesamos un lote de datos, debemos enviar este lote de datos al método descendente para su posterior procesamiento, pero la tasa de procesamiento del método descendente no está controlada y puede ser rápida o lenta. Si la tasa de procesamiento del método descendente es lenta, lo que ralentiza la tasa de procesamiento del método actual, ¿qué debo hacer en este momento?

Puede pensar en usar un grupo de subprocesos, que es una solución, pero necesita crear muchos subprocesos y tener en cuenta que los métodos posteriores no admiten la concurrencia.Si se trata de una tarea que requiere un uso intensivo de la CPU, el procesamiento de subprocesos múltiples puede ser más lento que el procesamiento de subproceso único porque se requiere un cambio de contexto frecuente.

En este momento, puede considerar usar BlockingQueue. El escenario de aplicación más típico de BlockingQueue es el modelo productor-consumidor anterior. El productor pone datos en la cola, el consumidor toma datos de la cola y usa BlockingQueue como una cola de búfer en el medio, lo que resuelve el problema de la velocidad asíncrona entre el productor y el consumidor.

imagen-20221003203703573.png

Puede pensar en la cola de mensajes (MessageQueue), la cola de mensajes es equivalente a una cola de bloqueo distribuida y BlockingQueue es equivalente a una cola de bloqueo local, que solo funciona en esta máquina. Correspondiente a caché distribuida (como: Redis, Memcache) y caché local (como: Guava, Caffeine).

Además, hay sombras de BlockingQueue en muchos marcos.Por ejemplo, en el grupo de subprocesos, BlockingQueue se usa para almacenar en búfer las tareas. Los métodos para enviar y extraer mensajes en la cola de mensajes también se toman prestados de BlockingQueue, y su uso es muy similar.

Hoy, analicemos en profundidad el código fuente subyacente de Queue.

2. Uso de BlockingQueue

El uso de BlockingQueue es muy simple, es decir, poner datos y obtener datos.

/**
 * @apiNote BlockingQueue示例
 * @author 一灯架构
 */
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        // 1. 创建队列,设置容量是10
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
        // 2. 往队列中放数据
        queue.put(1);
        // 3. 从队列中取数据
        Integer result = queue.take();
    }
}
复制代码

Para cumplir con diferentes escenarios de uso, BlockingQueue ha diseñado muchos métodos para poner y quitar datos.

funcionar Lanzar una excepción devolver un valor específico bloquear bloquear por un tiempo
poner datos add offer put offer(e, time, unit)
obtener datos remove poll take poll(time, unit)
Obtener datos (no eliminar) element() peek() no apoyo no apoyo

Las diferencias entre estos grupos de métodos son:

  1. Cuando la cola está llena y luego coloca datos en la cola, el método de agregar genera una excepción, el método de oferta devuelve falso, el método de colocación seguirá bloqueando (hasta que otro subproceso tome los datos de la cola), el método de oferta bloquea el tiempo especificado y luego devuelve falso.
  2. Cuando la cola está vacía y se toman datos de la cola, el método de eliminación lanza una excepción, el método de encuesta devuelve un valor nulo, el método de toma se bloqueará (hasta que otros subprocesos coloquen datos en la cola), el método de encuesta se bloquea durante un tiempo específico y luego devuelve nulo.
  3. Cuando la cola está vacía y luego va a la cola para ver los datos (sin eliminar los datos), el método del elemento genera una excepción y el método de observación devuelve un valor nulo.

Los métodos más utilizados en el trabajo son los métodos de oferta y encuesta que bloquean durante un tiempo específico.

3. Clase de implementación BlockingQueue

BlockingQueue comúnmente tiene las siguientes cinco clases de implementación, principalmente debido a diferentes escenarios de aplicación.

  • ArrayBlockingQueue

    La cola de bloqueo implementada en función de las matrices debe especificar el tamaño de la capacidad al crear la cola, que es una cola limitada.

  • LinkedBlockingQueue

    La cola de bloqueo implementada en función de la lista vinculada, la cola predeterminada es ilimitada, la capacidad se puede especificar al crear

  • SynchronousQueue

    Una cola de bloqueo sin búfer, los datos producidos deben consumirse inmediatamente

  • PriorityBlockingQueue

    Se implementa la cola de bloqueo con prioridad, según la visualización de datos, es una cola ilimitada

  • DelayQueue

    La cola de bloqueo que implementa la función de retraso, basada en PriorityQueue, es una cola ilimitada

4. Análisis del código fuente de BlockingQueue

Las cinco subclases de BlockingQueue se implementan de la misma manera.Esta vez, el ArrayBlockingQueue más utilizado se utiliza para el análisis del código fuente.

4.1 Propiedades de la clase ArrayBlockingQueue

Echemos un vistazo a las propiedades que hay en la clase ArrayBlockingQueue:

// 用来存放数据的数组
final Object[] items;

// 下次取数据的数组下标位置
int takeIndex;

// 下次放数据的数组下标位置
int putIndex;

// 当前已有元素的个数
int count;

// 独占锁,用来保证存取数据安全
final ReentrantLock lock;

// 取数据的条件
private final Condition notEmpty;

// 放数据的条件
private final Condition notFull;
复制代码

La implementación de los cuatro grupos de métodos de acceso a datos en ArrayBlockingQueue también es similar y, esta vez, los métodos de poner y tomar se utilizan para el análisis.

4.2 Análisis del código fuente del método put

imagen-20221003220940753.png

Ya sea que esté ingresando datos o extrayéndolos, comienza desde el principio de la línea y se mueve gradualmente hasta el final de la línea.

// 放数据,如果队列已满,就一直阻塞,直到有其他线程从队列中取走数据
public void put(E e) throws InterruptedException {
    // 校验元素不能为空
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
  	// 加锁,加可中断的锁
    lock.lockInterruptibly();
    try {
        // 如果队列已满,就一直阻塞,直到被唤醒
        while (count == items.length)
            notFull.await();
      	// 如果队列未满,就往队列添加元素
        enqueue(e);
    } finally {
      	// 结束后,别忘了释放锁
        lock.unlock();
    }
}

// 实际往队列添加数据的方法
private void enqueue(E x) {
    // 获取数组
    final Object[] items = this.items;
    // putIndex 表示本次插入的位置
    items[putIndex] = x;
    // ++putIndex 计算下次插入的位置
    // 如果下次插入的位置,正好等于队尾,下次插入就从 0 开始
    if (++putIndex == items.length)
        putIndex = 0;
  	// 元素数量加一
    count++;
    // 唤醒因为队列空等待的线程
    notEmpty.signal();
}
复制代码

Hay un diseño interesante en el código fuente, al agregar elementos, si se llega al final de la cola, la próxima vez se agrega desde la cabeza de la cola, lo que equivale a hacer una cola circular.

Me gusta lo siguiente:

imagen-20221003222232640.png

4.3 código fuente del método de toma

// 取数据,如果队列为空,就一直阻塞,直到有其他线程往队列中放数据
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
  	// 加锁,加可中断的锁
    lock.lockInterruptibly();
    try {
        // 如果队列为空,就一直阻塞,直到被唤醒
        while (count == 0)
            notEmpty.await();
        // 如果队列不为空,就从队列取数据
        return dequeue();
    } finally {
      	// 结束后,别忘了释放锁
        lock.unlock();
    }
}

// 实际从队列取数据的方法
private E dequeue() {
  	// 获取数组
    final Object[] items = this.items;
    // takeIndex 表示本次取数据的位置,是上一次取数据时计算好的
    E x = (E) items[takeIndex];
    // 取完之后,就把队列该位置的元素删除
    items[takeIndex] = null;
    // ++takeIndex 计算下次拿数据的位置
    // 如果正好等于队尾的话,下次就从 0 开始拿数据
    if (++takeIndex == items.length)
        takeIndex = 0;
    // 元素数量减一
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // 唤醒被队列满所阻塞的线程
    notFull.signal();
    return x;
}
复制代码

4.4 Resumen

  1. ArrayBlockingQueue es una cola de bloqueo implementada en base a matrices. Al crear una cola, debe especificar la capacidad y el tamaño. Es una cola limitada.
  2. La capa inferior de ArrayBlockingQueue tiene la forma de una cola circular para garantizar que la posición de la matriz se pueda reutilizar.
  3. El acceso a ArrayBlockingQueue está bloqueado por ReentrantLock para garantizar la seguridad de subprocesos y se puede usar con confianza en un entorno de subprocesos múltiples.
  4. Cuando utilice ArrayBlockingQueue, estime la longitud de la cola para asegurarse de que las tarifas del productor y del consumidor coincidan.

Soy "One Light Architecture". Si este artículo es útil para usted, le invitamos a que le guste, comente y preste atención a sus amigos. Gracias, viejos hierros. Nos vemos en el próximo número.

Supongo que te gusta

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