Depois de aprender sobre a fila de sincronização do java, bloqueio e mecanismo de notificação de espera, olhar para a fila de bloqueio tornará mais fácil entender a fila de bloqueio. Uma fila de bloqueio é uma fila que oferece suporte a duas operações adicionais. Essas duas operações adicionais suportam a inserção de bloqueio: quando a fila está cheia, a fila irá bloquear a thread que insere o elemento até que a fila não esteja cheia; remoção de bloqueio: quando a fila está vazia, a thread que obtém o elemento irá esperar que a fila se torne vazia Quando a fila de bloqueio não está disponível, essas duas operações adicionais fornecem 4 métodos de processamento
Método / método de processamento |
Lançar uma exceção |
Devolver valor especial |
Continua bloqueando |
Tempo limite de saída |
Método de inserção |
adicionar (e) |
oferta (e) |
colocar (e) |
oferta (e, tempo, unidade) |
Método de remoção |
retirar() |
votação() |
toma() |
poll (tempo, unidade) |
Método de inspeção |
elemento() |
olhadinha() |
- |
- |
Lançar uma exceção significa que, quando a fila está cheia, se você inserir um elemento na fila, uma exceção IllegalStateException ("Queue cheia") será lançada. Quando a fila está vazia, obter elementos da fila lançará NoSuchElementException.
Retornar um valor especial significa que quando um elemento é inserido na fila, ele retornará se o elemento foi inserido com sucesso e retornará verdadeiro se for bem-sucedido. Se for um método de remoção, ele pegará um elemento da fila e retornará nulo se não for.
· Sempre bloqueando significa que quando a fila de bloqueio está cheia, se o encadeamento do produtor colocar elementos na fila, a fila continuará bloqueando o encadeamento do produtor até que a fila esteja disponível ou saia em resposta a uma interrupção. Quando a fila está vazia, se o encadeamento do consumidor tirar elementos da fila, a fila bloqueará o encadeamento do consumidor até que a fila não esteja vazia. Apresentaremos o código-fonte deste modo mais tarde.
· A saída de tempo limite significa que quando a fila de bloqueio está cheia, se o encadeamento do produtor inserir elementos na fila, a fila bloqueará o encadeamento do produtor por um período de tempo.Se o tempo especificado for excedido, o encadeamento do produtor será encerrado.
O JDK nos fornece várias filas de bloqueio. Seus métodos de implementação são quase os mesmos. Descreveremos a implementação de filas de bloqueio de uma maneira mais tarde. A tabela a seguir mostra as várias filas de bloqueio e descrições fornecidas pelo JDK:
Fila de bloqueio | descrição |
ArrayBlockingQueue | Uma fila de bloqueio limitada composta por uma estrutura de array |
LinkedBlockingQueue | Uma fila de bloqueio limitada composta por uma estrutura de lista vinculada |
PriorityBlockingQueue | Uma fila de bloqueio ilimitada que suporta classificação de prioridade |
DelayQueue | Uma fila de bloqueio ilimitada implementada usando filas prioritárias |
SynchronousQueue | Uma fila de bloqueio que não armazena elementos |
LinkedTransferQueue | Uma fila de bloqueio ilimitada composta por uma estrutura de lista vinculada |
LinkedBlockingDeque | Uma fila de bloqueio bidirecional composta por uma estrutura de lista vinculada |
Como acima, para as filas de bloqueio fornecidas por jdk para nós, se olharmos seu código-fonte, descobrimos que suas definições de variáveis têm a interface Lock e a interface Condition como campos, ou seja, sua implementação é baseada em Lock e Condition. Contanto que dominemos o uso de Lock and Condition, é fácil dominar o uso de filas de bloqueio. Vamos pegar LinkedBlockingQueue como exemplo, e o código de definição é o seguinte:
/** Lock held by take, poll, etc */ take、poll操作时获取锁
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */等待take操作
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */ put offer时获取锁
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */ //等待put操作
private final Condition notFull = putLock.newCondition();
A seguir, temos que ver como LinkedBlockingQueue armazena e recupera elementos. Primeiro, tomamos o elemento de armazenamento como exemplo e tomamos o método put como exemplo. O código é o seguinte:
//唤醒一个take等操作的线程
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
public void put(E e) throws InterruptedException {
//如果传值为null,抛出异常
if (e == null) throw new NullPointerException();
int c = -1;
//创建节点
Node<E> node = new Node<E>(e);
//获取putLock实例
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//获取锁,可中断
putLock.lockInterruptibly();
try {
//如果容量已满
while (count.get() == capacity) {
//存放元素的线程等待
notFull.await();
}
//存入队列
enqueue(node);
c = count.getAndIncrement();
//如果队列不满,唤醒存入队列的线程
if (c + 1 < capacity)
notFull.signal();
} finally {
//释放锁
putLock.unlock();
}
//如果为0
if (c == 0)
//唤醒一个取出元素的线程
signalNotEmpty();
}
Introduzimos o código-fonte de armazenamento de elementos acima. A lógica de armazenamento de outros elementos é semelhante à anterior, por isso não os apresentaremos aqui. Vamos analisar o código-fonte para obter os elementos: método take. O código-fonte é o seguinte:
//唤醒一个put操作的线程
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
//获取take锁
takeLock.lockInterruptibly();
try {
//如果队列元素为空,获取线程等待
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
//如果队列元素不为空,唤醒取元素线程
if (c > 1)
notEmpty.signal();
} finally {
//释放锁
takeLock.unlock();
}
//如果c=capacity 唤醒put操作的线程
if (c == capacity)
signalNotFull();
return x;
}
Acima, apresentamos as operações put e take de LinkedBlockingQueue. Podemos saber que o princípio principal da fila de bloqueio é aguardar o mecanismo de notificação, usando os componentes Lock, Condition e LockSupport fornecidos pelo pacote simultâneo. Desde que estejamos familiarizados com o uso desses três componentes, podemos implementar facilmente nossa própria fila de bloqueio. Se você quiser saber a implementação específica de outras filas de bloqueio, pode consultar o código-fonte por conta própria.O núcleo é o mecanismo de notificação de espera e os três componentes mencionados acima.