关于BlockingQueue

一.BlockingQueue是什么

BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。

被阻塞的情况主要有如下两种:

(1)当队列满了的时候进行入队列操作;

(2)当队列空了的时候进行出队列操作;

java.util.concurrent 包里的 BlockingQueue是一个接口, 继承Queue接口,Queue接口继承 Collection。BlockingQueue——>Queue——>Collection。

因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;

同样,当一个线程试图对一个空队列进行出队列操作时,它将会被阻塞,除非有另一个线程进行了入队列操作。

BlockingQueue基于队列容器实现了插入和取出数据的阻塞,如果队列中没有数据时,取出数据的操作被阻塞直到队列中有数据能被取出;

如果队列中数据已经填满,则插入数据的操作将被阻塞,直到队列中有空位能够插入。

BlockingQueue实现是线程安全的,所有排队方法都使用内部锁或其他形式的并发控制以原子方式实现其效果,除非在实现中另有说明。

BlockingQueue是为了解决多线程中数据高效安全传输而提出的。

二.BlockingQueue能做什么

BlockingQueue通常用于一个线程生产对象,而另外一个线程消费这些对象的场景(现在ActiveMQ, kfaka, fqueue也属于这种场景)。

阻塞队列主要用在生产者/消费者的场景,下面这幅图展示了一个线程生产、一个线程消费的场景:

负责生产的线程不断的制造新对象并插入到阻塞队列中,直到达到这个队列的上限值。

队列达到上限值之后生产线程将会被阻塞,直到消费的线程对这个队列进行消费。

同理,负责消费的线程不断的从队列中消费对象,直到这个队列为空,当队列为空时,消费线程将会被阻塞,除非队列中有新的对象被插入。

三.BlockingQueue原理

BlockingQueue是一种队列,所以具备队列的三种基本方法:插入、删除、读取:

BlockingQueue只是增加了两类和阻塞相关的方法:put(e)take()offer(e, time, unit)poll(time, unit)

put(e)和take()方法会一直阻塞调用线程,直到线程被中断或队列状态可用;

offer(e, time, unit)和poll(time, unit)方法会限时阻塞调用线程,直到超时或线程被中断或队列状态可用。

除此之外,BlockingQueue还具有以下特点:

(1)BlockingQueue队列中不能包含null元素;

(2)BlockingQueue接口的实现类都必须是线程安全的,实现类一般通过“锁”保证线程安全;

(3)BlockingQueue 可以是限定容量的。remainingCapacity()方法用于返回剩余可用容量,对于没有容量限制的BlockingQueue实现,该方法总是返回Integer.MAX_VALUE 。

成员:

它们的区别主要体现在存储结构上或对元素操作上的不同,但是对于take与put操作的原理,却是类似的。

1、ArrayBlockingQueue(数组阻塞队列)

基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,

ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。

ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;

按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。

Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。

ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。

这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。

2、LinkedBlockingQueue(链阻塞队列)

基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),

当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;

只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,

反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,

这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

作为开发者,需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,

LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。

3、DelayQueue(延迟队列)

DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。

DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。

使用场景:DelayQueue使用场景较少,但都相当巧妙,常见的例子比如使用一个DelayQueue来管理一个超时未响应的连接队列。

4、PriorityBlockingQueue(具有优先级的队列)

基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。

因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。

在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。

5、SynchronousQueue(同步队列)

一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,

而消费者必须亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。

相对于有缓冲的BlockingQueue来说,少了一个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,

由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);

但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。

声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别:

如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;

但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,

而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。

6、LinkedTransferQueue(链无界阻塞队列)

一个由链表结构组成的无界阻塞队列,相当于其它队列,LinkedTransferQueue队列多了transfer和tryTransfer方法。

7、LinkedBlockingDeque(链阻塞双端队列)

一个由链表结构组成的双向阻塞队列。队列头部和尾部都可以添加和移除元素,多线程并发时,可以将锁的竞争最多降到一半。

四.BlockingQueue使用

BlockingQueue即阻塞队列,它是基于ReentrantLock,依据它的基本原理,可以实现Web中的长连接聊天功能,当然其最常用的还是用于实现生产者与消费者模式。

大致如下图所示:

BlockingQueue来实现生产者-消费者模式。在生产者-消费者模式中,一共有四类角色:生产者、消费者、消息队列、消息体。

1、Producer(生产者)

生产者生产消息体(Data),并将消息体(Data)传递给通道(Channel)。

/**
 * 生产者
 */
public class Producer implements Runnable {

    private Channel channel;

    public Producer(Channel channel) {
        this.channel = channel;
    }

    @Override
    public void run() {

        while (true) {
            String v = String.valueOf(ThreadLocalRandom.current().nextInt());
            Data data = new Data(v);
            try {
                channel.put(data);
                System.out.println(Thread.currentThread().getName() + " produce :" + data);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Thread.yield();
        }
    }
}

2、Consumer(消费者)

消费者从通道(Channel)中获取数据,进行处理。

/**
 * 消费者
 */
public class Consumer implements Runnable {
private final Channel channel; public Consumer(Channel channel) { this.channel = channel; } @Override public void run() { while (true) { try { Object obj = channel.take(); System.out.println(Thread.currentThread().getName() + " consume :" + obj.toString()); } catch (InterruptedException e) { e.printStackTrace(); } Thread.yield(); } } }

3、Channel(通道)

相当于消息的队列,对消息进行排队,控制消息的传输。

/**
 * 通道类
 */
public class Channel {

    private final BlockingQueue blockingQueue;


    public Channel(BlockingQueue blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    public Object take() throws InterruptedException {
        return blockingQueue.take();
    }

    public void put(Object o) throws InterruptedException {
        blockingQueue.put(o);
    }

}

4、Data(消息体/数据)

Data代表了实际生产或消费的数据。

/**
 * 数据/消息
 */
public class Data<T> implements Serializable {
private T data; public Data(T data) { this.data = data; } public T getData() { return data; } public void setData(T data) { this.data = data; } @Override public String toString() { return "Data{" + "data=" + data + '}'; } }

测试如下:

public class Main {
    public static void main(String[] args) {
        BlockingQueue blockingQueue = new SomeQueueImplementation();
        Channel channel = new Channel(blockingQueue);

        Producer p = new Producer(channel);
        Consumer c1 = new Consumer(channel);
        Consumer c2 = new Consumer(channel);

        new Thread(p).start();
        new Thread(c1).start();
        new Thread(c2).start();

    }
}
原文地址:https://www.cnblogs.com/ZJOE80/p/12887559.html