JUC - Multi-threaded blocking queue BlockingQueue (4)

1. Queue

A queue is a special linear table, which is special in that it only allows deletion operations at the front end of the table (front), and insertion operations at the back end (rear) of the table. Like a stack, a queue is a linear table with limited operations. The end of the insertion operation is called the tail of the queue, and the end of the deletion operation is called the head.

Inserting a queue element into the queue is called enqueue, and removing a queue element from the queue is called dequeue. Because the queue can only be inserted at one end and deleted at the other end, only the elements that enter the queue at the earliest can be deleted from the queue first, so the queue is also called FIFO (first in first out) linear table

2. Blocking queue

A blocking queue (BlockingQueue) is a queue that supports two additional operations. The two additional operations are:

When the queue is empty, the thread that gets the element blocks waiting for the queue to become non-empty

When the queue is full, the thread storing the element blocks waiting for the queue to become available

Blocking queues are often used in the scenario of producers and consumers. The producer is the thread that adds elements to the queue, and the consumer is the thread that takes elements from the queue. The blocking queue is the container where the producer stores elements, and the consumer only takes elements from the container

BlockingQueue interface inheritance diagram 

BlockingQueue interface implementation class 

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

output

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

(1) Blocking queue BlockingQueue four processing methods

method/handling method Throw an exception return special value keep blocking timeout exit
insert method add(E) offer(E) put(E) offer(E, long, TimeUnit)
removal method remove() poll() take() poll(long, TimeUnit)
Inspection Method element() peek() unavailable unavailable

The two methods that really reflect blocking are  put(E) / take()

  • Throw an exception
    When the queue is full, if you insert an element into the queue, a default IllegalStateException exception .
    When the queue is empty, if you get an element from the queue again, NoSuchElementException an exception

  • Return a special value
    Every time an insert operation is performed on the queue, it will return whether the element is inserted successfully. If it is successful, it will return. trueIf
    it is a get operation, it will get an element from the queue. If there is no such element, the return value will be null.

  • Always block
    When the queue is full, if the producer thread continues to putadd elements , the queue will block the producer thread until the queue is available or the response interrupt exits.
    When the queue nullis , if the consumer thread takeadds elements from the queue, The queue will block the consumer thread until the queue is no longer null.

  • Timeout exit
    When the blocking queue is full, if the producer thread continues to insert elements into the queue, the queue will block the producer thread for a period of time, and if the specified time is exceeded, the producer thread will exit

1. Throw an exception

When the queue is full, if you insert elements into the queue, a default IllegalStateException exception .
When the queue is empty, if you get elements from the queue again, NoSuchElementException an exception

/**
     * 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

add() method

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

remove() method

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

2. Return a special value

Every time an insert operation is performed on the queue, it will return whether the element is inserted successfully, and if it is successful, it will return. trueIf
it is an acquisition operation, it will obtain an element from the queue. If there is no such element, the return value will benull

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

The implementation of throwing an exception  and  returning a special value  method is the same, but the handling of failed operations is different

Through the source code of AbstractQueue, it can be found that add(e), remove(), element() are all implemented based on offer(), poll() and peek() respectively

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. Always blocked

When the queue is full, if the producer thread continues to add putelements , the queue will block the producer thread until the queue is available or the response interrupt exits
When the queue nullis , if the consumer thread adds takeelements to the queue, the queue will block Hold the consumer thread until the queue is no longernull

/**
     * 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. Timeout exit

When the blocking queue is full, if the producer thread continues to insert elements into the queue, the queue will block the producer thread for a period of time. If the specified time is exceeded, the producer thread will exit

/**
     * 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) BlockingQueue core method

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) Seven blocking queues in Java

ArrayBlockingQueue: a bounded blocking queue composed of an array structure
LinkedBlockingQueue: a bounded blocking queue composed of a linked list structure
PriorityBlockingQueue: an unbounded blocking queue that supports priority sorting DelayQueue: an unbounded blocking queue
implemented using a priority queue
SynchronousQueue: a A blocking queue that does not store elements
LinkedTransferQueue: an unbounded blocking queue composed of a linked list structure
LinkedBlockingDeque: a bidirectional blocking queue composed of a linked list structure

1、ArrayBlockingQueue

ArrayBlockingQueue is a bounded blocking queue implemented with an array. This queue orders elements on a first-in-first-out (FIFO) basis. By default, visitors are not guaranteed to access the queue fairly. The so-called fair access queue refers to all blocked producer threads or consumer threads. When the queue is available, the queue can be accessed in the order of blocking, that is, the blocked producer thread first , you can insert elements into the queue first, and the consumer thread that blocks first can get elements from the queue first. Usually, throughput is reduced in order to ensure fairness . We can create a fair blocking queue with the following code:

ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);

The fairness of its access is achieved through the ReentrantLock lock.

2、linkedBlockingQueue

linkedBlockingQueue is a bounded blocking queue implemented with a linked list. The default and maximum length of this queue is Integer.MAX_VALUE. This queue sorts the elements on a first-in, first-out basis.

3、PriorityBlockingQueue

PriorityBlockingQueue is an unbounded queue that supports priority. By default, the elements are arranged in natural order, and the sorting rules of the elements can also be specified through the comparator. Elements are sorted in ascending order.

4、DelayQueue

DelayQueue is an unbounded blocking queue that supports delayed retrieval of elements. The queue is implemented using PriorityQueue. The elements in the queue must implement the Delayed interface. When creating an element, you can specify how long it takes to get the current element from the queue. Elements are only taken from the queue when the delay expires. We can use DelayQueue in the following application scenarios:

The design of the cache system: DelayQueue can be used to save the validity period of the cached elements, and a thread can be used to query the DelayQueue in a loop. Once the elements can be obtained from the DelayQueue, it means that the cache validity period has arrived.

Timing task scheduling. Use DelayQueue to save the tasks and execution time that will be executed that day. Once the tasks are obtained from DelayQueue, they will be executed. For example, TimerQueue is implemented using DelayQueue.

How to implement the Delayed interface

We can refer to the ScheduledFutureTask class in ScheduledThreadPoolExecutor. This class implements the Delayed interface. First of all: when the object is created, use time to record when the object can be used before, the code is as follows:

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

}

Then use getDelay to query how long the current element needs to be delayed. The code is as follows:

1

2

3

public long getDelay(TimeUnit unit) {

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

    }

It can be seen from the constructor that the unit of the delay time parameter ns is nanoseconds. It is best to use nanoseconds when designing your own, because you can specify any unit when getDelay is used. Once nanoseconds are used as the unit, the delay time is less than accurate. Nanoseconds are troublesome. Please note that when the time is less than the current time, getDelay will return a negative number.

Finally, we can use time to specify its order in the queue, for example: put the one with the longest delay time at the end of the queue.

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

    }

How to implement delayed blocking queue

The implementation of the delay blocking queue is very simple. When the consumer gets the element from the queue, if the element does not reach the delay time, the current thread will be blocked.

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、SynchronousQueue

SynchronousQueue is a blocking queue that does not store elements. Each put operation must wait for a take operation, otherwise elements cannot be added. SynchronousQueue can be regarded as a passer, responsible for directly passing the data processed by the producer thread to the consumer thread. The queue itself does not store any elements, which is very suitable for transitive scenarios, such as data used in one thread, passed to another thread for use, and the throughput of SynchronousQueue is higher than that of

linkedBlockingQueue 和 ArrayBlockingQueue。

It supports fair access queues. The default policy mechanism is still unfair

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时可以初始化队列的容量,用来防止其再扩容时过渡膨胀。另外双向阻塞队列可以运用在“工作窃取”模式中

Guess you like

Origin blog.csdn.net/MinggeQingchun/article/details/127386484