Análise de código-fonte Java e perguntas da entrevista-ArrayBlockingQueue análise de código-fonte

Esta série de blog relacionado, Mu coluna referência de classe Java de código fonte e do sistema fabricantes entrevistador sucintamente Zhenti
abaixo desta coluna é o endereço GitHub:
Fonte resolvido: https://github.com/luanqiu/java8
artigo Demonstração: HTTPS: // GitHub.
colegas de classe com / luanqiu / java8_demo podem ver se necessário)

Análise de código-fonte Java e perguntas da entrevista-ArrayBlockingQueue análise de código-fonte

frase introdutória
Nesta seção introduzimos o capítulo termina com uma fila: ArrayBlockingQueue. De acordo com a tradução literal, o chinês é chamado de fila de bloqueio de array, pelo nome, sabemos que essa fila de bloqueio usa um array na parte inferior. Quando se trata de matrizes, você pode pensar em ArrayList e HashMap.No novo cenário, ArrayList encontra a nova posição de subscrito da matriz por meio do tamanho ++. HashMap calcula a posição de subscrito por meio do algoritmo hash, então ArrayBlockingQueue é o mesmo? Que tipo de método? Não, o ArrayBlockingQueue usa uma maneira muito maravilhosa, vamos esperar e ver.

Para maior conveniência das explicações, o chefe da equipe é o chefe da matriz e a cauda da equipe é a cauda da matriz.

1 Arquitetura geral

Podemos obter algumas informações úteis nos comentários da turma:

1.1 Notas de aula

  1. Matriz de bloqueio limitado, uma vez que a capacidade é criada, o tamanho subsequente não pode ser modificado;
  2. Os elementos são ordenados, classificados de acordo com a ordem de chegada, inserção de dados do final da equipe e coleta dados do chefe da equipe;
  3. Quando a fila estiver cheia, os dados colocados na fila serão bloqueados e, quando a fila estiver vazia, os dados na fila também serão bloqueados.

É possível observar nas notas da classe que ArrayBlockingQueue não é o mesmo que a classe geral da estrutura da matriz e não pode ser expandida dinamicamente.Se a fila estiver cheia ou vazia, o take and put será bloqueado.

1.2 Estrutura de dados

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

O código acima tem dois campos-chave, takeIndex e putIndex, que indicam respectivamente a posição do índice da próxima vez para coletar e colocar dados. Portanto, ao adicionar e coletar dados, você não precisa calcular, pode saber onde adicionar e onde obter os dados.

2 Inicialização

Durante a inicialização, existem dois parâmetros importantes: o tamanho da matriz e se é justo.O código fonte é o seguinte:

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 do código-fonte, podemos ver se o segundo parâmetro é justo, principalmente usado para ler e escrever bloqueio, é justo, se é bloqueio justo, então, na competição de bloqueio, ele estará na ordem de chegada, se o bloqueio for injusto, Aleatório durante contenção de bloqueio.

Para justiça e injustiça de bloqueio, damos um exemplo: por exemplo, a fila agora está cheia e há muitos threads executando operações de put, deve haver muitos threads bloqueando a espera; quando outros threads executam take, ele ativa o thread em espera, Se for um bloqueio justo, ele ativará os threads bloqueados pela ordem de bloqueio em espera.Se for um bloqueio injusto, ativará aleatoriamente os threads adormecidos.

Portanto, quando a fila está cheia e muitos encadeamentos executam a operação put, se é um bloqueio justo, a ordem na qual os elementos da matriz são adicionados é a ordem na qual os encadeamentos de bloqueio são liberados.Há uma ordem, não um bloqueio justo, porque a ordem na qual os encadeamentos de bloqueio são liberados é aleatória , Portanto, a ordem em que os elementos são inseridos na matriz não seguirá a ordem em que foram inseridos.

O mesmo acontece quando a fila está vazia.

ArrayBlockingQueue realiza facilmente a ordem de inserção dos elementos da matriz através da imparcialidade e injustiça dos bloqueios. Se você deseja alcançar essa função, o que você faria? Você vai pensar em usar a função de bloqueio? De fato, mencionamos essa idéia muitas vezes no artigo.Quando precisamos realizar uma coisa, primeiro verifique se a API existente pode ser satisfeita.Se possível, ela pode ser alcançada por herança e composição ArrayBlockingQueue 就是组合了锁的功能.

Durante a inicialização, se os dados originais forem fornecidos, observe que o tamanho dos dados originais deve ser menor que a capacidade da fila; caso contrário, uma exceção será lançada, conforme mostrado na figura a seguir:
Insira a descrição da imagem aqui
escrevemos uma demonstração e o erro é o seguinte:
Insira a descrição da imagem aqui

3 Novos dados

Novos dados serão adicionados de acordo com a posição do putIndex.O código fonte é o seguinte:

// 新增,如果队列满,无限阻塞
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();
}

No código-fonte, podemos ver que, na verdade, existem dois novos casos:

  1. A posição recém adicionada é centralizada e adicionada diretamente.A figura abaixo demonstra que putIndex está na posição do índice da matriz 5, e ainda não está no final da fila. Em seguida, você pode adicioná-lo diretamente. Calcule a próxima posição adicionada deve ser 6;
    Insira a descrição da imagem aqui
  2. A nova posição está no final da equipe, portanto, da próxima vez que você a adicionar, você começará do
    Insira a descrição da imagem aqui
    início.O diagrama esquemático é o seguinte: a figura acima mostra esta linha de código: if (++ putIndex == items.length) putIndex = 0;

Pode-se ver que ao adicionar ao final da equipe, a próxima adição será reiniciada desde o início da equipe.

4 Pegue os dados

Os dados são retirados do chefe da equipe.O código fonte é o seguinte:

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;
}

Pode ser visto no código-fonte que a posição de obter dados toda vez é a posição de takeIndex. Depois de encontrar os dados a serem coletados dessa vez, aumentará o takeIndex em 1 para calcular a posição do índice quando os dados forem obtidos na próxima vez. Se a posição de receber dados desta vez já for a cauda da equipe, a posição de receber dados da próxima vez começará do início, ou seja, a partir de 0.

5 Excluir dados

Excluir dados é muito interessante, vamos dar uma olhada no código fonte principal:

// 一共有两种情况:
// 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();
}

A situação de exclusão de dados é mais complicada. Existem dois casos. O primeiro caso é takeIndex == removeIndex. Vamos desenhar um diagrama esquemático para ver o método de processamento: o
Insira a descrição da imagem aqui
segundo caso é dividido em dois tipos:

  1. Se removeIndex + 1! = PutIndex, mova o próximo elemento para frente um por um, o diagrama esquemático é o seguinte:
    Insira a descrição da imagem aqui
  2. Se removeIndex + 1 == putIndex, modifique o valor de putIndex para a posição excluída, o diagrama esquemático é o seguinte:
    Insira a descrição da imagem aqui

O método de exclusão de ArrayBlockingQueue é realmente bastante complicado e precisa considerar muitos cenários especiais.

6 Resumo

A parte inferior de ArrayBlockingQueue é uma matriz delimitada.Em geral, não é muito diferente de outras filas.Note que takeIndex e putIndex atingem o final da fila, eles começarão a alternar novamente com 0. Isso é especial. Preste atenção especial ao estudar o código-fonte.

Publicado 40 artigos originais · ganhou elogios 1 · vista 4983

Acho que você gosta

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