JUC - Fila de bloqueio multiencadeada BlockingQueue (4)

1. Fila

Uma fila é uma tabela linear especial, que é especial porque permite apenas operações de exclusão no front-end da tabela (front) e operações de inserção no back-end (rear) da tabela. Como uma pilha, uma fila é uma tabela linear com operações limitadas. O final da operação de inserção é chamado de final da fila e o final da operação de exclusão é chamado de cabeça.

Inserir um elemento de fila na fila é chamado de enfileirar e remover um elemento de fila da fila é chamado de desenfileirar. Como a fila só pode ser inserida em uma extremidade e excluída na outra extremidade, apenas os elementos que entram na fila o mais cedo podem ser excluídos da fila primeiro, portanto, a fila também é chamada de tabela linear FIFO (primeiro a entrar, primeiro a sair ) .

2. Fila de bloqueio

Uma fila de bloqueio (BlockingQueue) é uma fila que suporta duas operações adicionais. As duas operações adicionais são:

Quando a fila está vazia, o thread que obtém os blocos de elemento aguardando que a fila se torne não vazia

Quando a fila está cheia, o thread que armazena os blocos de elemento aguardando a fila ficar disponível

As filas de bloqueio são frequentemente utilizadas no cenário de produtores e consumidores, sendo o produtor a thread que adiciona elementos à fila e o consumidor a thread que retira os elementos da fila. A fila de bloqueio é o container onde o produtor armazena os elementos, e o consumidor apenas pega os elementos do container

Diagrama de herança da interface BlockingQueue 

Classe de implementação da interface BlockingQueue 

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class BlockQueueTest {
    public static void main(String[] args) {
        BlockingQueue blockingQueue = new SynchronousQueue();

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + " put 1");
                blockingQueue.put("1");

                System.out.println(Thread.currentThread().getName() + " put 2");
                blockingQueue.put("2");

                System.out.println(Thread.currentThread().getName() + " put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();


        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+" take " + blockingQueue.take());

                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+" take " + blockingQueue.take());

                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+" take " + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

saída

T1 put 1
T2 take 1
T1 put 2
T2 take 2
T1 put 3
T2 take 3

(1) Fila de bloqueio BlockingQueue quatro métodos de processamento

método/método de manipulação Lançar uma exceção devolver valor especial continue bloqueando saída de tempo limite
inserir método adicionar(E) oferta(E) colocar(E) oferta(E, longo, TimeUnit)
método de remoção remover() enquete() pegar() enquete(longo, TimeUnit)
Método de inspeção elemento() olhadinha() indisponível indisponível

Os dois métodos que realmente refletem o bloqueio são  put(E) / take()

  • Lançar uma exceção
    Quando a fila estiver cheia, se você inserir um elemento na fila, uma IllegalStateException exceção .
    Quando a fila estiver vazia, se você obtiver um elemento da fila novamente, NoSuchElementException uma exceção

  • Retorna um valor especial
    Toda vez que uma operação de inserção é executada na fila, ela retornará se o elemento foi inserido com sucesso. Se for bem-sucedida, retornará. trueSe
    for uma operação get, obterá um elemento da fila. Se não houver tal elemento, o valor de retorno será null.

  • Bloquear sempre
    Quando a fila estiver cheia, se o encadeamento produtor continuar a putadicionar elementos , a fila bloqueará o encadeamento produtor até que a fila esteja disponível ou a interrupção da resposta saia .
    a fila, A fila bloqueará o thread do consumidor até que a fila não seja mais .nulltakenull

  • Saída de tempo limite
    Quando a fila de bloqueio estiver cheia, se o encadeamento do produtor continuar a inserir elementos na fila, a fila bloqueará o encadeamento do produtor por um período de tempo e, se o tempo especificado for excedido, o encadeamento do produtor sairá

1. Lance uma exceção

Quando a fila estiver cheia, se você inserir elementos na fila, uma IllegalStateException exceção .
Quando a fila estiver vazia, se você obter elementos da fila novamente, NoSuchElementException uma exceção

/**
     * 1、抛出异常
     * 当队列满的时候, 如果再向队列中插入元素, 会抛出默认的 IllegalStateException 异常.
     * 当队列为空时候, 如果再从队列中获取元素, 会抛出 NoSuchElementException 异常
     */
    public static void throwException(){
        BlockingQueue blockingQueue = new ArrayBlockingQueue(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));

        // java.lang.IllegalStateException: Queue full
        //System.out.println(blockingQueue.add("d"));

        // 获取队列首元素
        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());

        // java.util.NoSuchElementException
        //System.out.println(blockingQueue.remove());
    }
true
true
true
a
a
b
c

método add()

public abstract class AbstractQueue<E>
    extends AbstractCollection<E>
    implements Queue<E> {
    public boolean add(E e) {
        if (offer(e))
            return true;
        else
            throw new IllegalStateException("Queue full");
    }
}

método remove()

public E remove() {
        E x = poll();
        if (x != null)
            return x;
        else
            throw new NoSuchElementException();
    }

2. Retorne um valor especial

Toda vez que uma operação de inserção for realizada na fila, retornará se o elemento foi inserido com sucesso, e se for bem-sucedida, retornará. trueSe
for uma operação de aquisição, obterá um elemento da fila. Se houver nenhum tal elemento, o valor de retorno seránull

/**
     * 2、返回特殊值
     * 每次对队列执行插入操作, 会返回元素是否插入成功, 成功则返回 true.
     * 如果是获取操作, 则是从队列中获取一个元素, 没有这个元素的话, 返回值为 null
     */
    public static void returnSpecial(){
        BlockingQueue blockingQueue = new ArrayBlockingQueue(3);

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));

        // 队列满了,返回false;不抛出异常
        System.out.println(blockingQueue.offer("d"));

        // 获取队列首元素
        System.out.println(blockingQueue.peek());

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());

        // 队列为空,返回null;不抛出异常
        System.out.println(blockingQueue.poll());
    }

A implementação de lançar uma exceção  e  retornar um  método de valor especial é a mesma, mas o tratamento de operações com falha é diferente

Através do código-fonte do AbstractQueue, pode-se descobrir que add(e), remove(), element() são todos implementados com base em offer(), poll() e peek() respectivamente

public boolean add(E arg0) {
		if (this.offer(arg0)) {
			return true;
		} else {
			throw new IllegalStateException("Queue full");
		}
	}
 
	public E remove() {
		Object arg0 = this.poll();
		if (arg0 != null) {
			return arg0;
		} else {
			throw new NoSuchElementException();
		}
	}
 
	public E element() {
		Object arg0 = this.peek();
		if (arg0 != null) {
			return arg0;
		} else {
			throw new NoSuchElementException();
		}
	}
    public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            // 如果原来队列为空,重置leader线程,通知available条件
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }
 
    //因为DelayQueue不限制长度,因此添加元素的时候不会因为队列已满产生阻塞,因此带有超时的offer方法的超时设置是不起作用的
    public boolean offer(E e, long timeout, TimeUnit unit) {
        // 和不带timeout的offer方法一样
        return offer(e);
    }


    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            E first = q.peek();
            if (first == null || first.getDelay(TimeUnit.NANOSECONDS) > 0)
                return null;
            else
                return q.poll();
        } finally {
            lock.unlock();
        }
    }

3. Sempre bloqueado

Quando a fila estiver cheia, se o encadeamento produtor continuar a adicionar putelementos , a fila bloqueará o encadeamento produtor até que a fila esteja disponível ou a interrupção da resposta
saia Quando a fila nullfor , se o encadeamento consumidor adicionar takeelementos à fila, a fila irá bloquear Segure a thread do consumidor até que a fila não esteja maisnull

/**
     * 3、一直阻塞
     * 当队列满的时候, 如果生产者线程继续向队列中 put 元素, 队列将会一直阻塞生产者线程, 直到队列可用或者响应中断退出.
     * 当队列为 null 的时候, 如果消费者线程从队列中 take 元素, 队列会阻塞住消费者线程, 直到队列不为 null
     */
    public static void alwaysBlock() throws InterruptedException {
        BlockingQueue blockingQueue = new ArrayBlockingQueue(3);

        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");

        // 程序一直等待
        blockingQueue.put("d");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());

        // 程序一直等待
        System.out.println(blockingQueue.take());
    }

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

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

4. Saída de tempo limite

Quando a fila de bloqueio estiver cheia, se o encadeamento produtor continuar a inserir elementos na fila, a fila bloqueará o encadeamento produtor por um período de tempo. Se o tempo especificado for excedido, o encadeamento produtor sairá

/**
     * 4、超时退出
     * 当阻塞队列满时, 如果生产者线程继续向队列中插入元素, 队列会阻塞生产者线程一段时间
     * 如果超过了这个指定的时间, 生产者线程就会退出
     */
    public static void overTimeQuit() throws InterruptedException {
        BlockingQueue blockingQueue = new ArrayBlockingQueue(3);

        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");

        // 队列满了,超时2S退出
        blockingQueue.offer("d", 2,TimeUnit.SECONDS);

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());

        // 队列为空,超时2S退出
        System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
    }
public boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException {

        checkNotNull(e);
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length) {
                if (nanos <= 0)
                    return false;
                nanos = notFull.awaitNanos(nanos);
            }
            enqueue(e);
            return true;
        } finally {
            lock.unlock();
        }
    }

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

(2) Método central BlockingQueue

public interface BlockingQueue<E> extends Queue<E> {
 
    //将给定元素设置到队列中,如果设置成功返回true, 否则抛出异常。如果是往限定了长度的队列中设置值,推荐使用offer()方法。
    boolean add(E e);
 
    //将给定的元素设置到队列中,如果设置成功返回true, 否则返回false. e的值不能为空,否则抛出空指针异常。
    boolean offer(E e);
 
    //将元素设置到队列中,如果队列中没有多余的空间,该方法会一直阻塞,直到队列中有多余的空间。
    void put(E e) throws InterruptedException;
 
    //将给定元素在给定的时间内设置到队列中,如果设置成功返回true, 否则返回false.
    boolean offer(E e, long timeout, TimeUnit unit)
        throws InterruptedException;
 
    //从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。
    E take() throws InterruptedException;
 
    //在给定的时间里,从队列中获取值,如果没有取到会抛出异常。
    E poll(long timeout, TimeUnit unit)
        throws InterruptedException;
 
    //获取队列中剩余的空间。
    int remainingCapacity();
 
    //从队列中移除指定的值。
    boolean remove(Object o);
 
    //判断队列中是否拥有该值。
    public boolean contains(Object o);
 
    //将队列中值,全部移除,并发设置到给定的集合中。
    int drainTo(Collection<? super E> c);
 
    //指定最多数量限制将队列中值,全部移除,并发设置到给定的集合中。
    int drainTo(Collection<? super E> c, int maxElements);
}

(3) Sete filas de bloqueio em Java

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 oferece suporte à classificação por prioridade
DelayQueue: uma fila de bloqueio ilimitada implementada usando uma fila de prioridade
SynchronousQueue: a Uma fila de bloqueio que não armazena elementos
LinkedTransferQueue: uma fila de bloqueio ilimitada composta por uma estrutura de lista encadeada
LinkedBlockingDeque: uma fila de bloqueio bidirecional composta por uma estrutura de lista encadeada

1、ArrayBlockingQueue

ArrayBlockingQueue é uma fila de bloqueio limitada implementada com uma matriz. Essa fila ordena os elementos com base no primeiro a entrar, primeiro a sair (FIFO). Por padrão, não é garantido que os visitantes acessem a fila de forma justa. A chamada fila de acesso justo refere-se a todos os threads produtores ou threads consumidores bloqueados. Quando a fila está disponível, a fila pode ser acessada na ordem de bloqueio, ou seja, o encadeamento produtor bloqueado primeiro, você pode inserir elementos na fila primeiro, e o encadeamento consumidor que bloqueia primeiro pode obter elementos da fila primeiro. Normalmente, o throughput é reduzido para garantir a imparcialidade . Podemos criar uma fila de bloqueio justo com o seguinte código:

ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);

A imparcialidade de seu acesso é obtida por meio do bloqueio ReentrantLock.

2、linkedBlockingQueue

linkedBlockingQueue é uma fila de bloqueio limitada implementada com uma lista encadeada. O comprimento padrão e máximo desta fila é Integer.MAX_VALUE. Essa fila classifica os elementos primeiro a entrar, primeiro a sair.

3、PriorityBlockingQueue

PriorityBlockingQueue é uma fila ilimitada que suporta prioridade. Por padrão, os elementos são organizados em ordem natural e as regras de classificação dos elementos também podem ser especificadas por meio do comparador. Os elementos são classificados em ordem crescente.

4、DelayQueue

DelayQueue é uma fila de bloqueio ilimitada que suporta recuperação atrasada de elementos. A fila é implementada usando PriorityQueue. Os elementos na fila devem implementar a interface Delayed.Ao criar um elemento, você pode especificar quanto tempo leva para obter o elemento atual da fila. Os elementos são retirados da fila apenas quando o atraso expira. Podemos usar DelayQueue nos seguintes cenários de aplicação:

O design do sistema de cache: DelayQueue pode ser usado para salvar o período de validade dos elementos em cache, e um thread pode ser usado para consultar o DelayQueue em um loop. Uma vez que os elementos podem ser obtidos do DelayQueue, isso significa que o cache prazo de validade chegou.

Agendamento de tarefas de temporização. Use DelayQueue para salvar as tarefas e o tempo de execução que serão executadas naquele dia. Uma vez que as tarefas são obtidas de DelayQueue, elas serão executadas. Por exemplo, TimerQueue é implementado usando DelayQueue.

Como implementar a interface Delayed

Podemos nos referir à classe ScheduledFutureTask em ScheduledThreadPoolExecutor. Essa classe implementa a interface Delayed. Primeiro de tudo: quando o objeto for criado, use o tempo para registrar quando o objeto pode ser usado antes, o código é o seguinte:

1

2

3

4

5

6

ScheduledFutureTask(Runnable r, V result, long ns, long period) {

      super(r, result);

      this.time = ns;

      this.period = period;

      this.sequenceNumber = sequencer.getAndIncrement();

}

Em seguida, use getDelay para consultar quanto tempo o elemento atual precisa ser atrasado. O código é o seguinte:

1

2

3

public long getDelay(TimeUnit unit) {

 return unit.convert(time - now(), TimeUnit.NANOSECONDS);

    }

Pode ser visto no construtor que a unidade do parâmetro de tempo de atraso ns é nanossegundos. É melhor usar nanossegundos ao projetar o seu próprio, porque você pode especificar qualquer unidade quando getDelay é usado. Uma vez que nanossegundos são usados ​​como a unidade, o o tempo de atraso é menos preciso. Nanossegundos são problemáticos. Observe que quando o tempo for menor que o horário atual, getDelay retornará um número negativo.

Por fim, podemos usar o tempo para especificar sua ordem na fila, por exemplo: coloque aquele com o maior tempo de atraso no final da fila.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public int compareTo(Delayed other) {

      if (other == this)

 return 0;

      if (other instanceof ScheduledFutureTask) {

 ScheduledFutureTask x = (ScheduledFutureTask)other;

 long diff = time - x.time;

 if (diff < 0)

   return -1;

 else if (diff > 0)

   return 1;

    else if (sequenceNumber < x.sequenceNumber)

   return -1;

 else

   return 1;

      }

    long d = (getDelay(TimeUnit.NANOSECONDS)-other.getDelay(TimeUnit.NANOSECONDS));

      return (d == 0) ? 0 : ((d < 0) ? -1 : 1);

    }

Como implementar a fila de bloqueio atrasada

A implementação da fila de bloqueio de atraso é muito simples, quando o consumidor recebe o elemento da fila, se o elemento não atingir o tempo de atraso, o thread atual será bloqueado.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

long delay = first.getDelay(TimeUtil.NANOSECONDS);

if(delay<=0){

 return q.poll ;//阻塞队列

}else if(leader!=null){

  //leader表示一个等待从阻塞队列中取消息的线程

  available.await(); //让线程进入等待信号

}else {

//当leader为null,则将当前线程设置为leader

Thread thisThread = Thread.currentThread();

try{

leader = thisThread;

//使用awaitNanos()方法让当前线程等待接收信号或等待delay时间

available.awaitNanos(delay);

}finally{

 if(leader==thisThread){

   leader=null;

   }

 }

}

5、Fila Síncrona

SynchronousQueue é uma fila de bloqueio que não armazena elementos. Cada operação put deve esperar por uma operação take, caso contrário, os elementos não podem ser adicionados. SynchronousQueue pode ser considerado um passador, responsável por passar diretamente os dados processados ​​pela thread produtora para a thread consumidora. A própria fila não armazena nenhum elemento, o que é muito adequado para cenários transitivos, como dados usados ​​em um encadeamento, passados ​​para outro encadeamento para uso e a taxa de transferência de SynchronousQueue é maior do que

linkedBlockingQueue e ArrayBlockingQueue.

Suporta filas de acesso justo. O mecanismo de política padrão ainda é injusto

6、linkedTransferQueue

linkedTransferQueue是一个由链表结构组成的无界阻塞TransferQueue队列。相对于其他阻塞队列linkedTransferQueue多了tryTransfer和transfer方法。

transfer方法

如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。

tryTransfer方法

是用来试探下生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回false。和transfer方法的区别是tryTransfer方法无论消费者是否接收,方法立即返回。而transfer方法是必须等到消费者消费了才返回。

对于带有时间限制的tryTransfer(E e, long timeout, TimeUnit unit)方法,则是试图把生产者传入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时还没消费元素,则返回false,如果在超时时间内消费了元素,则返回true。

7、linkedBlockingDeque

linkedBlockingDeque是一个由链表结构组成的双向阻塞队列。所谓双向队列指的你可以从队列的两端插入和移出元素。双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其他的阻塞队列,linkedBlockingDeque多了addFirst,addLast,offerFirst,offerLast,peekFirst,peekLast等方法,以First单词结尾的方法,表示插入,获取(peek)或移除双端队列的第一个元素。以Last单词结尾的方法,表示插入,获取或移除双端队列的最后一个元素。另外插入方法add等同于addLast,移除方法remove等效于removeFirst。但是take方法却等同于takeFirst,不知道是不是Jdk的bug,使用时还是用带有First和Last后缀的方法更清楚。在初始化linkedBlockingDeque时可以初始化队列的容量,用来防止其再扩容时过渡膨胀。另外双向阻塞队列可以运用在“工作窃取”模式中

Acho que você gosta

Origin blog.csdn.net/MinggeQingchun/article/details/127386484
Recomendado
Clasificación