转载请注明源出处:http://www.cnblogs.com/lighten/p/7427763.html

1.前言

  JDK5是一个重要的更新版本,其提供了大量的并发类。之前的介绍都是一些util下早期的集合类,本章开始介绍JDK5提供的并发包中所给出的在多线程下,线程安全的集合类。首先介绍的是Queue队列,之前就介绍了2个:ArrayDeque和PriorityQueue。队列算是天然需要线程安全的,因为其特性适用场景大多数都是并发操作,进行排队。所以JDK5并发包中提供了大量不同特性的线程安全的队列。本章从ArrayBlockingQueue开始介绍。

2.ArrayBlockingQueue

  ArrayBlockingQueue的数据结构和ArrayDeque十分相似,但是多了一些并发所需要的字段,比如锁。默认使用的所是非公平锁。

  初始化了容量和锁的相关设置。

  offer方法可以队列是不允许出现空的对象。使用的方法也是加锁来确保线程安全,如果队列满了就会返回false,入队列失败。

  入队列也是放入目前要放的位置,再找到下一个要放的位置,整个都是通过count计数来判断队列是否满了,也不用担心覆盖了数据,因为入队列的时候判断了当前数量是否等于容量。最后发出队列非空信号。

  poll方法会取出队列的数据,如果count是0,自然是null。否则进行出队列操作。

  取出当前下标的内容,并置为null,找到下一个要取出的位置。数量减一,迭代器存在也会处理取出迭代器。然后发出一个队列非满信号。

  之前所讲的非空和非满信号还是有所作用的。

  放入一个对象时,如果队列满了但是还是想放进去,就需要等待队列有空位。队列在移除一个对象时就会发出一个非满信号。上面的放入方法就是用到了这个信号。如果队列满了其会等待非满信号,直到超时。如果不希望超时,就是想放进去,可以使用put方法,不过其可能会抛出中断异常。

  poll方法一样,如果队列为空自然取不到,就一直等待一个非空的信号。不希望超时可以使用take方法。

  值得一提的是该类的迭代器,所有的迭代器共享数据,队列改变会影响所有的迭代器。为了保证正确,增加了许多复杂的操作,但是由于循环数组和一些内部移除会导致迭代器丢失它们的位置,或显示一些它们不应该显示的元素。为了避免这个情况,当队列有一个或多个迭代器的时候,其通过以下手段保持状态:

  1.跟踪循环的次数。即 takeIndex为0的次数。

  2.每当删除一个内部元素时,通过回调通知所有迭代器(因此其他元素也可以移动)。

  以上JDK注解难以理解,看代码。queue中有一个字段是itrs,其存放了目前所创建的所有迭代器。

  每个迭代器都是一个节点,节点是虚引用。

  迭代器创建的时候会加入到这个itrs中。doSomeSweeping是用来清理无用的迭代器。在迭代器创建和detach的时候会触发。sweeper字段就是记录上次扫描到的位置。如果为null,就从链表头开始扫描,有就从其下一个开始扫描。如果找到了一个被回收了或者是耗尽的迭代器,就清理掉它,重新建立链表的连接,继续找下一个。这就完成了对无效迭代器的清理了。

  之前我们都看到,在出队列的时候,调用了itrs的remove方法,下面是该方法做的具体事情:

  队列中数量为0的时候,队列就是空的,会将所有迭代器进行清理并移除。否者如果takeIndex的下标是0,意味着队列从尾中取完了,又回到头部获取,此时的操作就是上面JDK注解1条件,实质上跟踪的是队列的循环次数,计数器cycles自增1,然后对所有的迭代器进行清理,清除耗尽了的迭代器。

  下面仔细研究一下ArrayBlockingQueue的迭代器实现,看看其是共享队列数据的含义是什么。还是从迭代器的构造函数看起:

  count等于0的时候,创建的这个迭代器是个无用的迭代器,可以直接移除,进入detach模式。否则就把当前队列的读取位置给迭代器当做下一个元素,cursor存储下个元素的位置。

  hashNext()先判断nextItem有没有值了,没有值的时候触发noNext函数,这个函数做的就是调整修正队列。这里先提一下并发中迭代器会发生什么问题。ArrayBlockingQueue的实现是一个循环数组,使用takeIndex和putIndex来控制元素的出入队列。这样就产生了一个问题,迭代器在创建的时候,其位置已经确定,但是队列可能在不断的出入队列,这样迭代器会受到严重影响,可能造成队列实际上入出循环了数组一圈,而迭代器记录的是上一圈的情况,只有下标,这样遍历就会造成很大的问题。所以才需要上面所说的2点来保证,第一个就是每个迭代器记录当前其循环的圈数,第二个就是队列元素出队列时影响所有的迭代器。这个修正队列就是在当前迭代器遍历完成之后,比较一下圈数来判断具体情况,圈数和队列读取下标和迭代器读取下标来判断是否要废弃该迭代器。

  next方法也会在迭代器没有被废弃的时候比较一下当前队列的情况。之后就是更新上次的下标和游标已经下一个值。

  其它的方法就不再描述,现在给一些测试用例,来印证上面所描述的内容,更为直观的表现其迭代器的使用。由于有锁实际上也是顺序操作,所以例子在单线程中模拟。

    @Test
public void test() {
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);
queue.offer(1);
queue.offer(2); // 放入2个元素
Iterator<Integer> it1 = queue.iterator();
Iterator<Integer> it2 = queue.iterator();
Iterator<Integer> it3 = queue.iterator();
Iterator<Integer> it4 = queue.iterator();
Iterator<Integer> it5 = queue.iterator();
while(it1.hasNext()) {
System.out.print(it1.next()+",");
}
System.out.println();
queue.poll(); // 清除第一个元素,再放入数据
queue.offer(3); // 填充一个元素,测试可以追上的情况,队列中有2,3
while(it2.hasNext()) {
System.out.print(it2.next()+",");
}
System.out.println();
queue.poll(); // 再移除一个元素,此时1,2元素都移除了
// 迭代器无法获取元素2,但是队列只相差一圈
while(it3.hasNext()){
System.out.print(it3.next()+",");
}
System.out.println();
queue.offer(4); // 队列中有3,4
queue.poll();
while(it4.hasNext()) {
System.out.print(it4.next()+",");
}
System.out.println();
queue.poll();
queue.offer(5);
while(it5.hasNext()) {
System.out.print(it5.next()+",");
}
}

  这5个迭代器都是在队列满的时候生成的,之后第一个迭代器正常遍历,第2个迭代器是在队列出了一个元素,又入了一个元素的时候,此时还是出现了所有的元素,队列依旧是满的。第3个迭代器是又出了一个元素,队列中只剩下3,迭代器依旧没影响,还是输出了1,3。第4个迭代器放入一个元素,又移除一个元素,此时应该还剩4,输出的也是1,4。问题在第5个迭代器,移除了一个元素,再添加一个元素,此时应该是从新回到位置2次了,所以5只输出了一个1。这个就判断迭代器失效了。再放入一个元素测试是一样的。

  可能会有疑问,迭代器5先是12,然后34,最后填到5的时候才废弃,不是超了两圈?实际上,如果队列中有1,2 全部取出队列,再填3,这样一圈迭代器就只能输出1了就被废弃了。

  上面只是个人的研究,实际上队列使用迭代器的需求也不算大。使用到的时候就需要好好研究一下了。

Java之集合(十六)ArrayBlockingQueue的更多相关文章

  1. Java进阶(三十六)深入理解Java的接口和抽象类

    Java进阶(三十六)深入理解Java的接口和抽象类 前言 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类.这两者有太多相似的地方,又有太 ...

  2. “全栈2019”Java多线程第二十六章:同步方法生产者与消费者线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  3. “全栈2019”Java多线程第十六章:同步synchronized关键字详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  4. “全栈2019”Java异常第十六章:Throwable详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java异 ...

  5. 201671010140. 2016-2017-2 《Java程序设计》java学习第十六周

    java学习第十六周-并发        本周,学习了Java中线程,并发的知识,在老师的带领下,进行了对知识的理解学习,以及对实验的运行讲解,对这一块内容掌握的还可以,在自主编程中,也能够完成.线, ...

  6. “全栈2019”Java第七十六章:静态、非静态内部类访问权限

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  7. “全栈2019”Java第五十六章:多态与字段详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  8. “全栈2019”Java第四十六章:继承与字段

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. “全栈2019”Java第三十六章:类

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

随机推荐

  1. Vim配置(转)

    1.按F5可以直接编译并执行C.C++.java代码以及执行shell脚本,按“F8”可进行C.C++代码的调试 2.自动插入文件头 ,新建C.C++源文件时自动插入表头:包括文件名.作者.联系方式. ...

  2. UESTC 486 Good Morning (水题+坑!)

    题意:给你一行字符串,让你找其中蕴含的“good morning"的次数. 析:看起来很水么,多简单,只有统计一下其中字母的出现的次数,然后除以相应的个数. 但是很不幸的是WA,而且是在te ...

  3. HDU1072 Nightmare(BFS) 2016-07-24 14:02 40人阅读 评论(0) 收藏

    Nightmare Problem Description Ignatius had a nightmare last night. He found himself in a labyrinth w ...

  4. android周期性任务

    一般任务调度机制的实现方式主要有: Thread sleep.Timer.ScheduledExecutor.Handler和其他第三方开源库.android的AlarmManager 1. Time ...

  5. COM是如何实现STA的

    Rather than using thread synchronization objects (mutexes, semaphores, and so forth) to control acce ...

  6. Java返回当前对象的好处

    自己使用了一个第三方框架,发现非常的灵活,于是去研究了下,才知道是返回当前对象,才可以做到,例如以下案例: // 可以不断的点出很多函数 Glide.with(PhotoPagerSlitherAct ...

  7. WinForm&&DEV知识小结

    -------------------------------------------------------------------------------- 1.父窗体Form1中调用子窗体For ...

  8. ChicagoBoss简介

    翻译人:李罗琦 (ChicagoBoss)[http://www.chicagoboss.org] 由 Evan Miller创作,使用erlang/OTP的开发者们可以通过它提供的一个类似于Ruby ...

  9. Nginx an upstream response is buffered to a temporary file

    1.错误日志:warn:an upstream response is buffered to a temporary file 解决办法:增加fastcgi_buffers 8 4K;     fa ...

  10. 如何: 在 VS中的设计时刻主从表绑定控件到数据库

    这个示例展示了如何在 Visual Studio 2005 的设计时刻,把一个 data-aware 控件 (XtraGrid.XtraPivotGrid.XtraVerticalGrid 等) 绑定 ...