(13)多线程与并发库之java5阻塞队列(BlockingQueue)的应用----子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程循环100次,如此循环50次...

首先复习一下:

1,内部类的实例方法不能在静态中创建。

2,成员变量 在什么情况情况下才有值啊?在创建了类的实例对象的时候,才分配空间。这时,就需要一个构造方法来进行赋值。

3,static{


}这种语法,不需要创建实例对象 就可以运行,而bq1,bq2 是对象创建后的成员变量,对象里面的变量,所以不可应用静态代码块。

要用 这种匿名构造方法{ } 它的运行时机 在任何构造方法之前,只要调用 构造方法,匿名构造方法都会执行,也就是说 创建了几个对象,匿名构造方法就会执行几次。

而 静态代码块 是在 类加载的时候 执行。且 只调用一次。


分析:

本例介绍一个特殊的队列:BlockingQueue,如果BlockQueue是空的,从BlockingQueue取东西的操作将会被阻断进入等待状态,直到BlockingQueue进了东西才会被唤醒.同样,如果BlockingQueue是满的,任何试图往里存东西的操作也会被阻断进入等待状态,直到BlockingQueue里有空间才会被唤醒继续操作.


首先开启两个线程,

第一轮:queue1 刚开始为空的,所以它就想放入(put)数据。

而queue2刚开始为满的,这时他也想放入(put)数据,所以queue2就堵塞在那里了。

第二轮:1线程的queue2这时取出(take)数据,哈哈,这时 2 线程上的queue2就可以走了。

如图:



说明:阻塞对列和Semaphore有些类似,但也不同,阻塞队列是一方存数据,另一方取数据。而Semaphire通常是由同一方设置和释放信号量。

java.util.concurrent
接口 BlockingQueue<E>


类型参数:
E - 在此 collection 中保持的元素类型
所有超级接口:
Collection<E>, Iterable<E>, Queue<E>
所有已知子接口:
BlockingDeque<E>
所有已知实现类:
ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue

public interface BlockingQueue<E>
extends Queue<E>
 

支持两个附加操作的 Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。

BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(nullfalse,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:

抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
检查element()peek()不可用不可用

BlockingQueue 不接受null 元素。试图addputoffer 一个null 元素时,某些实现会抛出NullPointerExceptionnull 被用作指示poll 操作失败的警戒值。

【1】BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个remainingCapacity,超出此容量,便无法无阻塞地put 附加元素。没有任何内部容量约束的BlockingQueue 总是报告Integer.MAX_VALUE 的剩余容量。

【2】BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持Collection 接口。因此,举例来说,使用remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常 会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。

【3】BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAllcontainsAllretainAllremoveAll没有 必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了c 中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。

【4】BlockingQueue 实质上 支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的end-of-streampoison 对象,并根据使用者获取这些对象的时间来对它们进行解释。

以下是基于典型的生产者-使用者场景的一个用例。注意,BlockingQueue 可以安全地与多个生产者和多个使用者一起使用。

class Producer implements Runnable { private final BlockingQueue queue; Producer(BlockingQueue q) { queue = q; } public void run() { try { while(true) { queue.put(produce()); } } catch (InterruptedException ex) { ... handle ...} } Object produce() { ... } } class Consumer implements Runnable { private final BlockingQueue queue; Consumer(BlockingQueue q) { queue = q; } public void run() { try { while(true) { consume(queue.take()); } } catch (InterruptedException ex) { ... handle ...} } void consume(Object x) { ... } } class Setup { void main() { BlockingQueue q = new SomeQueueImplementation(); Producer p = new Producer(q); Consumer c1 = new Consumer(q); Consumer c2 = new Consumer(q); new Thread(p).start(); new Thread(c1).start(); new Thread(c2).start(); } }
内存一致性效果:当存在其他并发 collection 时,将对象放入 BlockingQueue 之前的线程中的操作 happen-before 随后通过另一线程从 BlockingQueue 中访问或移除该元素的操作。


示例代码:

package com.itm.thread; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /********* * * 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程循环100次,如此循环50次,请写出程序 * * * @author * */ public class BlockingQueueCommunication3 { public static void main(String[] args) { final Business business = new Business(); new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 50; i++) { // business.sub(i); } } }).start(); // 主线程。 for (int i = 1; i <= 50; i++) { // business.main(i); } } static class Business { // 产生 两个队列。 BlockingQueue<Integer> bq1 = new ArrayBlockingQueue<Integer>(1); BlockingQueue<Integer> bq2 = new ArrayBlockingQueue<Integer>(1); { try { bq2.put(1); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void sub(int i) { try { bq1.put(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } for (int j = 1; j <= 10; j++) { System.out.println("sub thread sequece of " + j + ", loop of " + i); } try { bq2.take();// 这样就可以 让第二个线程 把数据放进去了 bq2就可以执行了。 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public synchronized void main(int i) { try { bq2.put(1); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }// 不能让它走,就要往bq2中 提前放入数据。在还没有运行之前 就给他放入一个数据,就应该在构造方法中 放入。 for (int j = 1; j <= 100; j++) { System.out.println("main thread sequece of " + j + ", loop of " + i); } try { bq1.take();// 通知 bq1去取数据 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
运行结果:

死锁。

原因:没有把synchronized 关键字 去掉,所以造成了死锁。



使用BlockingQueue的关键技术点如下:

1.BlockingQueue定义的常用方法如下:

1)add(anObject):把anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则招聘异常

2)offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false.

3)put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里面有空间再继续.

4)poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null

5)take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到Blocking有新的对象被加入为止

2.BlockingQueue有四个具体的实现类,根据不同需求,选择不同的实现类

1)ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的.

2)LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的

3)PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序.

4)SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的.

3.LinkedBlockingQueue和ArrayBlockingQueue比较起来,它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值