主要内容

ArrayBlockingQueue.

这篇博客主要说说ArrayBlockingQueue这个阻塞队列的存储结构以及针对多线程的情况,这个阻塞队列是怎样实现多线程安全的,还有就是一些方法的区别。
其实对于多线程我也是慢慢体会到的,看了源码,看了别人的实现方式,才渐渐懂得怎样处理多线程安全的问题。
ArrayBlocking采用的是数组的存储方式。
希望你们也是如此。

当然在说的过程,采用源码来说是比较有权威的。

ArrayBlockingQueue

ArrayBlockingQueue是采用的是循环数组的形式表达队列,所以在该类中不存在扩容的情况,对于数组的长度来说,根据初始化的参数为标准,类中没有默认的数组长度。
其实对于这个类是怎样来实现多线程安全的呢,从这个类的成员就可以猜想到。

public class ArrayBlockingQueue<E> extends AbstractQueue<E>  implements BlockingQueue<E>, java.io.Serializable {final Object[] items;//存放元素的数组int takeIndex;//记录元素被取出元素的数组下标int putIndex;//记录元素被放入元素的数组下标int count;//记录元素的个数final ReentrantLock lock;//锁,同样也是保证多线程安全的一个重要因素private final Condition notEmpty;//notEmpty是当前lock的阻塞队列//作用就是采用内部的一个Condition队列来存储想通过put进行添加元素,但由于数组已满而被阻塞的线程。private final Condition notFull;//notFull是当前lock的另一个阻塞队列//作用就是采用内部的Condition队列来存储想通过take进行取出元素,但由于数组为空而被阻塞的线程。transient Itrs itrs = null;
}

刚才说,可以通过成员来推测出这个类采用的什么手段来实现多线程。其实我是看完之后才进行总结的,不是我一开始看的时候就知道。

那我们一步一步来看,
这个类给我们提供了很多添加和取出的方法,而多线程安全的处理当然是在方法上进行处理的。
那为什么说通过成员就可以看出来实现手段呢,
int count 这个成员是用来记录数组中当前拥有的元素的个数,那么通过添加和取出都要对这个变量进行操作,而这个成员并没有采用volitile进行修饰。但是该类采用了一个ReentrantLock lock,那我们就可以得出结论,这个类在所有的方法上都采用lock进行多线程安全的保证。所以对于count来说可以不添加任何有关多线程安全的修饰。

为什么这么说呢,那首先就要对ReentrantLock 有一定的认识,如果不了解请看博客ReentrantLock.相信你就会理解。
因为我们知道一个ReentrantLock 对象对应一个AQS,所以对于ArrayBlockingQueue来说,如果你对一个ArrayBlockingQueue对象进行操作(加入或者取出),那当然对于这个对象来说也只有一个ReentrantLock 对象作为其ArrayBlockingQueue类的成员。
同样是因为在ArrayBlockingQueue类中的加入和取出的方法中都要对其lock进行获取,而lock对于一个ArrayBlockingQueue对象来说只有一个,那么在多线程进行调用加入或者取出的方法的时候,自然会排队进行,因为对于多线程来说,他们认识的是同一把锁lock,当有线程在执行加入或者取出的方法的时候,自然会持有这把锁,那么对于其他线程来说就只能加入到lock锁的等待队列中,等一个线程执行完,释放锁唤醒等待队列中的等待线程的时候,那些等待线程才会去挨个执行他们的操作。

所以对于ArrayBlockingQueue的成员count 没有采用任何的修饰,只是一个普通的变量,因为对于每个线程对ArrayBlockingQueue 对象所执行的操作都是顺序的,所以count具有准确性。

那我们来看ArrayBlockingQueue这个类中给我们提供的操作方法。
不是白看的,你们找这些个方法的一个共同点。你们找完之后,我会慢慢说这些个方法的具体情况。

poll();
 public E poll() {final ReentrantLock lock = this.lock;lock.lock();//这里的lock就是ArrayBlockingQueue类中唯一的锁lock。try {return (count == 0) ? null : dequeue();} finally {lock.unlock();}}
offer(E e)
public boolean offer(E e) {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lock();try {if (count == items.length)return false;else {enqueue(e);return true;}} finally {lock.unlock();}}
put(E e)
 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();}}
take()
 public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}}

找出来了吗?
通过上边的方法,我们也能清楚的看到,每个方法开头都采用了lock.lock,方法的结尾都采用了lock.unlock。
所以可以说对于ArrayBlockingQueue来说,实现多线程的手段比较粗鲁,就整了一个ReentrantLock 对象作为成员,就可以让多线程在方法的执行上做到同步了,同样也就实现类多线程安全。
所以ArrayBlockingQueue类中还有其他成员
final Object[] items;
int takeIndex;
int putIndex;
例如这三个成员,也都没有任何的关于线程安全的修饰,因为一个lock就做到了。

下面我们来说说offer()和put()的区别,take() 和poll() 的区别

put() 和 offer()

说到offer你们可以回看上面的方法,对于offer来说,当一个线程调用offer的时候,如果此时数组已经满员了,那么offer会直接返回false。表示不能够添加
而对于put()来说,同样也是添加,但是就不一样了,当一个线程调用put()的时候,如果此时数组已经满员了,那么你回看上边的方法,你会看到 notFull.await();这样的代码,此时对于调用put方法的这个线程来说,会进行阻塞,而不是像offer一样直接返回false。这个nofull成员我在一开始的时候已经说过了。
其实在这个notfull所带领的阻塞队列中的线程表明都是等待添加的线程,如果你看过我写的博客Condition.你就会知道Condition阻塞队列的原理。然后就等待被唤醒了。

take() 和 poll()。
poll方法可以回看上边的源码,表示取出一个元素,当数组为空时,则直接返回null。
而take方法,倒是和put方法有的一拼,当数组为空时,会执行notEmpty.await();这个代码,表明将调用take方法的线程进行阻塞。但对于区操作,是将操作的这个线程阻塞起来,加入到notEmpty带领的这个阻塞队列中。然后就等待被唤醒了。

其实不管是notEmpty和是notFull,这两个Condition对象都是由lock这一个对象生成的,说明不管是在两个阻塞队列中任何一个阻塞线程,在被唤醒之后都是要进入到lock的等待队列中的。因为不管在什么时候,只有可能是一个线程持有锁。

既然说到了阻塞线程需要被唤醒,何时被唤醒?

对于put来说,表明数组中没有位置,所以无法put,只能阻塞,当一旦有poll操作的时候,poll方法会调用一个dequeue方法这个方法就是将数组中最老的元素进行取出,关键是在最后有一句 notFull.signal();
采用这句进行唤醒在notFull阻塞队列中的线程,被唤醒的线程加入到lock的等待队列中,等待机会执行。

 private E dequeue() {final Object[] items = this.items;E x = (E) items[takeIndex];items[takeIndex] = null;if (++takeIndex == items.length)takeIndex = 0;count--;if (itrs != null)itrs.elementDequeued();notFull.signal();return x;}

对于take来说,数组为空时,无法获取,进行阻塞,当一旦有offer操作的时候,offer操作会调用enqueue()方法,进行元素的添加,在最后进行notEmpty.signal();唤醒等待取操作的线程,被唤醒的线程加入到lock的等待队列中,等待机会执行。

 private void enqueue(E x) {// assert lock.getHoldCount() == 1;// assert items[putIndex] == null;final Object[] items = this.items;items[putIndex] = x;if (++putIndex == items.length)putIndex = 0;count++;notEmpty.signal();}

总结:
对于ArrayBlockingQueue类来说,采用实现多线程安全的手法还是比较简单,但同时也是我们可能最常用的一种手段,最主要的还是ArrayBlockingQueue应用了Reentrantlock这个类来进行实现的,还有Condition与Reentrantlock的结合。ArrayBlockingQueue中对元素的添加和删除的本质对循环数组的操作我们都很熟悉,最主要的还是能够理解Reentrantlock和Condition这两个类的原理,这样再回头自己看这个应用这两个类的阻塞队列的实现就会变得简单很多。

ArrayBlockingQueue 实现多线程安全 —————— 开开开山怪相关推荐

  1. Ruthless的java多线程学习总结

    2019独角兽企业重金招聘Python工程师标准>>> Java多线程-线程的同步与锁 Java多线程-线程的调度(休眠) Java多线程-线程的调度(优先级) Java多线程-线程 ...

  2. ArrayList线程不安全与Vector线程安全

    原因解释 首先说一下什么是线程不安全:线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用.不会出现数据不一致或者 ...

  3. 开发工具总结(7)之多年珍藏的Android开发必备网站和工具

    今天早上在简书上瞎逛,看到了这个,干货很多,这肯定是出自一个经验丰富的程序员之手,作为小小白,学习路上难免有需要帮助的和通过一些捷径来提高开发效率,所以收藏了这篇文章,同时也增加了一些自己平时收藏的内 ...

  4. 多线程学习-基础(十三)(学习参考·网摘) ArrayBlockingQueue源代碼解析(base jdk 1.8)...

    前记: 这个得首先声明一下,以下大部分内容均参考于:https://blog.csdn.net/wx_vampire/article/details/79585794,本随笔只作为学习作用,侵权删! ...

  5. Java核心知识点学习----多线程中的阻塞队列,ArrayBlockingQueue介绍

    1.什么是阻塞队列? 所谓队列,遵循的是先进先出原则(FIFO),阻塞队列,即是数据共享时,A在写数据时,B想读同一数据,那么就将发生阻塞了. 看一下线程的四种状态,首先是新创建一个线程,然后,通过s ...

  6. Java多线程-新特征-阻塞队列ArrayBlockingQueue

    阻塞队列是Java5线程新特征中的内容,Java定义了阻塞队列的接口java.util.concurrent.BlockingQueue,阻塞队列的概念是,一个指定长度的队列,如果队列满了,添加新元素 ...

  7. JUC多线程:阻塞队列ArrayBlockingQueue与LinkedBlockingQueue

    一.什么是阻塞队列: 阻塞队列最大的特性在于支持阻塞添加和阻塞删除方法: 阻塞添加:当阻塞队列已满时,队列会阻塞加入元素的线程,直到队列元素不满时才重新唤醒线程执行加入元素操作. 阻塞删除:但阻塞队列 ...

  8. 40个Java多线程问题总结

    (转) 这篇文章作者写的真是不错 40个问题汇总 1.多线程有什么用? 一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡.所谓"知其然知其所 ...

  9. 想进大厂?50个多线程面试题,你会多少?(一)

    最近看到网上流传着,各种面试经验及面试题,往往都是一大堆技术题目贴上去,而没有答案. 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让 ...

  10. java多线程三之线程协作与通信实例

    多线程的难点主要就是多线程通信协作这一块了,前面笔记二中提到了常见的同步方法,这里主要是进行实例学习了,今天总结了一下3个实例: 1.银行存款与提款多线程实现,使用Lock锁和条件Condition. ...

最新文章

  1. 阿里云服务器ecs绑定域名,端口的问题,不用80端口
  2. Java实现二叉树的构建与遍历
  3. SPOJ 130 - Rent your airplane and make money(dp+优化)
  4. 考研数学早年真题整理20题(很有可能重考!!)
  5. 从国内的源使用pip安装库,提高安装速度
  6. 深度学习-机器学习(5.1支持向量机)
  7. PS教程第三课:PS界面
  8. Notepad++使用教程
  9. Python | 这是过七夕吗?这是趁机学习一下下,就一下~~
  10. Bootstrap3 排版之水平对齐
  11. prop()和attr()
  12. 《Cortex-M0权威指南》之体系结构---嵌套中断控制器(NVIC)
  13. ansi mysql_MySQL的ANSI和Unicode驱动程序之间的区别
  14. 聊一聊使用airtest-selenium做Web自动化的常见问题
  15. 解决Robot Framework运行时没有Log的方案
  16. 建立积分兑换商城的意义
  17. 在GPU上运行MATLAB程序
  18. 笔记本控制台开启热点
  19. java冒泡排序(java冒泡排序经典代码)
  20. weibo4j中用到的mysql2bean的java工具

热门文章

  1. 新手小白安装linux系统
  2. OMPL官方教程学习State Validity Checking
  3. HTML语言剖析15:调色原理
  4. C#实现多人语音聊天
  5. 干货|浅谈iOS端短视频SDK技术实现
  6. Lua二进制chunk
  7. Android编程制作漫画,画出自己的漫画 Android漫画风制作所
  8. day2 标识符 字面值 变量 数据类型
  9. 计算机在线作业题,精选《计算机原理与结构》在线作业试题
  10. 阿里云Oss搭建私人图床