Análisis del código fuente Java y preguntas de la entrevista: análisis del código fuente ArrayBlockingQueue

Esta serie de blog relacionado, Mu columna de referencia de clase Java de código fuente y del sistema fabricantes entrevistador sucinta Zhenti
por debajo de esta columna es la dirección de GitHub:
Fuente resolvió: https://github.com/luanqiu/java8
artículo Demostración: HTTPS: // GitHub. com / luanqiu / java8_demo los
compañeros de clase pueden verlo si es necesario)

Análisis del código fuente Java y preguntas de la entrevista: análisis del código fuente ArrayBlockingQueue

frase introductoria
En esta sección se presenta el capítulo concluye con una cola: ArrayBlockingQueue. Según la traducción literal, el chino se llama cola de bloqueo de matriz. Por el nombre, sabemos que esta cola de bloqueo utiliza una matriz en la parte inferior. Cuando se trata de matrices, puede pensar en ArrayList y HashMap. En el nuevo escenario, ArrayList encuentra la nueva posición de subíndice de matriz a través de size ++. HashMap calcula la posición de subíndice a través del algoritmo hash, entonces ArrayBlockingQueue es el mismo? Que tipo de metodo No, ArrayBlockingQueue utiliza una forma muy maravillosa, esperaremos y veremos.

Para la conveniencia de la explicación, el jefe del equipo es el jefe de la matriz, y la cola del equipo es la cola de la matriz.

1 Arquitectura general

Podemos obtener información útil de los comentarios de la clase:

1.1 Apuntes de clase

  1. Conjunto de bloqueo limitado, una vez que se crea la capacidad, el tamaño posterior no se puede modificar;
  2. Los elementos están ordenados, ordenados según primero en entrar, primero en salir, insertar datos del final del equipo y tomar datos del jefe del equipo;
  3. Cuando la cola está llena, los datos colocados en la cola se bloquearán, y cuando la cola esté vacía, los datos en la cola también se bloquearán.

Se puede ver en las notas de clase que ArrayBlockingQueue no es lo mismo que la clase de estructura de matriz general, y no se puede expandir dinámicamente. Si la cola está llena o vacía, se bloqueará la toma y colocación.

1.2 Estructura de datos

// 队列存放在 object 的数组里面
// 数组大小必须在初始化的时候手动设置,没有默认大小
final Object[] items;
 
// 下次拿数据的时候的索引位置
int takeIndex;
 
// 下次放数据的索引位置
int putIndex;
 
// 当前已有元素的大小
int count;
 
// 可重入的锁
final ReentrantLock lock;
 
// take的队列
private final Condition notEmpty;
 
// put的队列
private final Condition notFull;

El código anterior tiene dos campos clave, takeIndex y putIndex, que indican respectivamente la posición del índice de la próxima vez para tomar datos y ponerlos. Entonces, al agregar datos y tomar datos, no necesita calcular, puede saber dónde agregar y dónde obtener los datos.

2 Inicialización

Durante la inicialización, hay dos parámetros importantes: el tamaño de la matriz y si es justo. El código fuente es el siguiente:

public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    // 队列不为空 Condition,在 put 成功时使用
    notEmpty = lock.newCondition();
    // 队列不满 Condition,在 take 成功时使用
    notFull =  lock.newCondition();
}

A partir del código fuente, podemos ver si el segundo parámetro es justo, se usa principalmente para leer y escribir el bloqueo es justo, si es un bloqueo justo, entonces, en la competencia de bloqueo, será por orden de llegada, si es un bloqueo injusto, Aleatorio durante la contención de bloqueo.

Para garantizar la imparcialidad y la imparcialidad del bloqueo, damos un ejemplo: por ejemplo, la cola ahora está llena y hay muchos subprocesos que realizan operaciones de colocación, debe haber muchos subprocesos que bloqueen la espera, cuando otros subprocesos ejecutan tomar, despertará el subproceso en espera Si es una cerradura justa, despertará los hilos bloqueados en el orden de bloqueo de espera. Si es una cerradura injusta, despertará aleatoriamente los hilos dormidos.

Por lo tanto, cuando la cola está llena y muchos subprocesos realizan la operación de colocación, si se trata de un bloqueo justo, el orden en que se agregan los elementos de la matriz es el orden en que se libera el subproceso de bloqueo. , Por lo tanto, el orden en que se insertan los elementos en la matriz no seguirá el orden en que se insertaron.

Lo mismo es cierto cuando la cola está vacía.

ArrayBlockingQueue realiza fácilmente el orden de inserción de los elementos de la matriz a través de la equidad e injusticia de los bloqueos. Si desea lograr esta función, ¿qué haría? ¿Pensarás en usar la función de bloqueo? De hecho, hemos mencionado esta idea muchas veces en el artículo. Cuando necesitemos lograr una cosa, primero observe si se puede satisfacer la API existente. Si es posible, se puede lograr a través de la herencia y la composición ArrayBlockingQueue 就是组合了锁的功能.

Durante la inicialización, si se proporcionan los datos originales, debe tenerse en cuenta que el tamaño de los datos originales debe ser menor que la capacidad de la cola; de lo contrario, se generará una excepción, como se muestra en la siguiente figura:
Inserte la descripción de la imagen aquí
escribimos una demostración y el error es el siguiente:
Inserte la descripción de la imagen aquí

3 nuevos datos

Se agregarán nuevos datos de acuerdo con la posición de putIndex. El código fuente es el siguiente:

// 新增,如果队列满,无限阻塞
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) {
    // assert lock.getHoldCount() == 1; 同一时刻只能一个线程进行操作此方法
    // assert items[putIndex] == null;
    final Object[] items = this.items;
    // putIndex 为本次插入的位置
    items[putIndex] = x;
    // ++ putIndex 计算下次插入的位置
    // 如果下次插入的位置,正好等于队尾,下次插入就从 0 开始
    if (++putIndex == items.length)
        putIndex = 0;
    count++;
    // 唤醒因为队列空导致的等待线程
    notEmpty.signal();
}

Del código fuente, podemos ver que en realidad hay dos casos nuevos:

  1. La posición recién agregada se centra y se agrega directamente. La siguiente figura demuestra que putIndex se encuentra en la posición del índice de matriz de 5 y aún no está al final de la cola. Luego se puede agregar directamente.
    Inserte la descripción de la imagen aquí
  2. La nueva posición está al final del equipo, por lo que la próxima vez que la agregue, comenzará desde el principio. El diagrama esquemático es el siguiente: la
    Inserte la descripción de la imagen aquí
    imagen de arriba muestra esta línea de código: if (++ putIndex == items.length) putIndex = 0;

Se puede ver que al agregar al final del equipo, la próxima adición se reiniciará desde el comienzo del equipo.

4 Toma los datos

Los datos se toman del jefe del equipo. El código fuente es el siguiente:

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        // 如果队列为空,无限等待
        // 直到队列中有数据被 put 后,自己被唤醒
        while (count == 0)
            notEmpty.await();
        // 从队列中拿数据
        return dequeue();
    } finally {
        lock.unlock();
    }
}
 
private E dequeue() {
    final Object[] items = this.items;
    // takeIndex 代表本次拿数据的位置,是上一次拿数据时计算好的
    E x = (E) items[takeIndex];
    // 帮助 gc
    items[takeIndex] = null;
    // ++ takeIndex 计算下次拿数据的位置
    // 如果正好等于队尾的话,下次就从 0 开始拿数据
    if (++takeIndex == items.length)
        takeIndex = 0;
    // 队列实际大小减 1
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    // 唤醒被队列满所阻塞的线程
    notFull.signal();
    return x;
}

Se puede ver en el código fuente que la posición de tomar datos cada vez es la posición de takeIndex. Después de encontrar los datos que se tomarán esta vez, aumentará takeIndex en 1 para calcular la posición del índice la próxima vez que se tomen los datos. Hay un caso especial. Si la posición de tomar datos esta vez ya es la cola del equipo, entonces la posición de tomar datos la próxima vez comenzará desde el principio, es decir, desde 0.

5 Eliminar datos

Eliminar datos es muy interesante, echemos un vistazo al código fuente central:

// 一共有两种情况:
// 1:删除位置和 takeIndex 的关系:删除位置和 takeIndex 一样,比如 takeIndex 是 2, 而要删除的位置正好也是 2,那么就把位置 2 的数据置为 null ,并重新计算 takeIndex 为 3。
// 2:找到要删除元素的下一个,计算删除元素和 putIndex 的关系
// 如果下一个元素不是 putIndex,就把下一个元素往前移动一位
// 如果下一个元素是 putIndex,把 putIndex 的值修改成删除的位置
void removeAt(final int removeIndex) {
    final Object[] items = this.items;
    // 情况1 如果删除位置正好等于下次要拿数据的位置
    if (removeIndex == takeIndex) {
        // 下次要拿数据的位置直接置空
        items[takeIndex] = null;
        // 要拿数据的位置往后移动一位
        if (++takeIndex == items.length)
            takeIndex = 0;
        // 当前数组的大小减一
        count--;
        if (itrs != null)
            itrs.elementDequeued();
    // 情况 2
    } else {
        final int putIndex = this.putIndex;
        for (int i = removeIndex;;) {
            // 找到要删除元素的下一个
            int next = i + 1;
            if (next == items.length)
                next = 0;
            // 下一个元素不是 putIndex
            if (next != putIndex) {
                // 下一个元素往前移动一位
                items[i] = items[next];
                i = next;
            // 下一个元素是 putIndex
            } else {
                // 删除元素
                items[i] = null;
                // 下次放元素时,应该从本次删除的元素放
                this.putIndex = i;
                break;
            }
        }
        count--;
        if (itrs != null)
            itrs.removedAt(removeIndex);
    }
    notFull.signal();
}

La situación de eliminar datos es más complicada. Hay dos casos. El primer caso es takeIndex == removeIndex. Dibujemos un diagrama esquemático para ver el método de procesamiento: el
Inserte la descripción de la imagen aquí
segundo caso se divide en dos tipos:

  1. Si removeIndex + 1! = PutIndex, mueve el siguiente elemento hacia adelante en uno, el diagrama esquemático es el siguiente:
    Inserte la descripción de la imagen aquí
  2. Si removeIndex + 1 == putIndex, modifica el valor de putIndex a la posición eliminada, el diagrama esquemático es el siguiente:
    Inserte la descripción de la imagen aquí

El método de eliminación de ArrayBlockingQueue es bastante complicado y debe considerar muchos escenarios especiales.

6 Resumen

La parte inferior de ArrayBlockingQueue es una matriz limitada. En general, no es muy diferente de otras colas. Cabe señalar que cuando takeIndex y putIndex llegan al final de la cola, comenzarán a pasar de cero nuevamente. Esto es especial. Presta especial atención al estudiar el código fuente.

Publicado 40 artículos originales · ganado elogios 1 · vistas 4983

Supongo que te gusta

Origin blog.csdn.net/aha_jasper/article/details/105525628
Recomendado
Clasificación