为了账号安全,请及时绑定邮箱和手机立即绑定

BlockingQueue讲解

标签:
Java

BlockingQueue讲解:

1 BlockingQueue概述:

BlocingQueue,顾名思义:<font color=red>阻塞队列</font>.BlockingQueue是在java.util.concurrent下的,因此不难理解,BlockingQueue是为了解决多线程中数据高效安全传输而提出的。  
阻塞队列所谓的“阻塞”,指的是<font color=red>某些情况下线程会挂起(即阻塞),一旦条件满足,被挂起的线程又会自动给唤醒。</font>使用BlockingQueue,不需要关心什么时候需要阻塞线程,什么时候需哟啊唤醒线程,这些内容BlockingQueue都已经做好了。

2 BlockingQueue中的方法:

方法摘要 boolean    add(E e)           将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则抛出 IllegalStateException。 boolean    contains(Object o)           如果此队列包含指定元素,则返回 true。 int    drainTo(Collection<? super E> c)           移除此队列中所有可用的元素,并将它们添加到给定 collection 中。 int    drainTo(Collection<? super E> c, int maxElements)           最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。 boolean    offer(E e)           将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则返回 false。 boolean    offer(E e, long timeout, TimeUnit unit)           将指定元素插入此队列中,在到达指定的等待时间前等待可用的空间(如果有必要)。 E  poll(long timeout, TimeUnit unit)           获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。 void   put(E e)           将指定元素插入此队列中,将等待可用的空间(如果有必要)。 int    remainingCapacity()           返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的附加元素数量;如果没有内部限制,则返回 Integer.MAX_VALUE。 boolean    remove(Object o)           从此队列中移除指定元素的单个实例(如果存在)。 E  take()           获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。

3 ArrayBlockingQueue

基于数组的阻塞队列,<font color=red>必须制定队列大小</font>。ArrayBlocingQueue中只有一个ReetrantLock对象,这意味着生产者和消费者无法并行。另外创建ArrayBlockingQueue时,可以指定ReetrantLock是否为公平锁,默认采用非公平锁。

    /** Main lock guarding all access */    final ReentrantLock lock;    /** Condition for waiting takes */    private final Condition notEmpty;    /** Condition for waiting puts */    private final Condition notFull;

LinkedBlockingQueue

  /** Lock held by take, poll, etc */    private final ReentrantLock takeLock = new ReentrantLock();    /** Wait queue for waiting takes */    private final Condition notEmpty = takeLock.newCondition();    /** Lock held by put, offer, etc */    private final ReentrantLock putLock = new ReentrantLock();    /** Wait queue for waiting puts */    private final Condition notFull = putLock.newCondition();

基于链表的阻塞队列,和ArrayBlockingQueue差不多。不过LinkedBlockingQueue如果不指定队列容量大小,会默认一个类似无限大小的容量,之所以说是类似是因为这个无限大小是Integer.MAX_VALUE,这么说就好理解ArrayBlockingQueue为什么必须要制定大小了,如果ArrayBlockingQueue不指定大小的话就用Integer.MAX_VALUE,那将造成大量的空间浪费,但是基于链表实现就不一样的,一个一个节点连起来而已。另外,LinkedBlockingQueue生产者和消费者都有自己的锁(见下面的代码),这意味着生产者和消费者可以"同时"运行。

5 SynchronousQueue

一种没有缓冲的等待队列。  
什么叫做没有缓存区:  
ArrayBlockingQueue中有:

/** The queued items  */private final E[] items;

数组用以存储队列  
LinkedBlockingQueue:

/** * Linked list node class */static class Node<E> {    /** The item, volatile to ensure barrier separating write and read */    volatile E item;    Node<E> next;    Node(E x) { item = x; }}

将队列以链表形式连接。  
生产者/消费者操作数据实际上都是通过这两个"中介"来操作数据的,但是SynchronousQueue则是生产者直接把数据给消费者(消费者直接从生产者这里拿数据),好像又回到了没有生产者/消费者模型的老办法了。换句话说,每一个插入操作必须等待一个线程对应的移除操作。SynchronousQueue又有两种模式:

1、公平模式

采用公平锁,并配合一个FIFO队列(Queue)来管理多余的生产者和消费者

2、非公平模式

采用非公平锁,并配合一个LIFO栈(Stack)来管理多余的生产者和消费者,这也是SynchronousQueue默认的模式

利用BlockingQueue实现生产者消费者模型:

public static void main(String[] args){    final BlockingQueue<String> bq = new ArrayBlockingQueue<String>(10);    Runnable producerRunnable = new Runnable()    {        int i = 0;        public void run()        {            while (true)            {                try                {                    System.out.println("我生产了一个" + i++);                    bq.put(i + "");                    Thread.sleep(1000);                }                 catch (InterruptedException e)                {                    e.printStackTrace();                }            }        }    };    Runnable customerRunnable = new Runnable()    {        public void run()        {            while (true)            {                try                {                    System.out.println("我消费了一个" + bq.take());                    Thread.sleep(3000);                }                 catch (InterruptedException e)                {                    e.printStackTrace();                }            }        }    };    Thread producerThread = new Thread(producerRunnable);    Thread customerThread = new Thread(customerRunnable);    producerThread.start();    customerThread.start();}运行结果:我生产了一个0 2 我消费了一个1 3 我生产了一个1 4 我生产了一个2 5 我消费了一个2 6 我生产了一个3 7 我生产了一个4 8 我生产了一个5 9 我消费了一个310 我生产了一个611 我生产了一个712 我生产了一个813 我消费了一个414 我生产了一个915 我生产了一个1016 我生产了一个1117 我消费了一个518 我生产了一个1219 我生产了一个1320 我生产了一个1421 我消费了一个622 我生产了一个1523 我生产了一个1624 我消费了一个725 我生产了一个1726 我消费了一个827 我生产了一个18

分两部分来看输出结果:

1、第1行~第23行。这块BlockingQueue未满,所以生产者随便生产,消费者随便消费,基本上都是生产3个消费1个,消费者消费速度慢

2、第24行~第27行,从前面我们可以看出,生产到16,消费到6,说明到了ArrayBlockingQueue的极限10了,这时候没办法,生产者生产一个ArrayBlockingQueue就满了,所以不能继续生产了,只有等到消费者消费完才可以继续生产。所以之后的打印内容一定是一个生产者、一个消费者

这就是前面一章开头说的<font color=red>"通过平衡生产者和消费者的处理能力来提高整体处理数据的速度"</font>,这给例子应该体现得很明显。另外,也不要担心非单一生产者/消费者场景下的系统假死问题,缓冲区空、缓冲区满的场景BlockingQueue都是定义了不同的Condition,所以不会唤醒自己的同类。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 1
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消