并发包阻塞队列之ArrayBlockingQueue

 

jdk1.7.0_79 

  上一节中对并发包中的非阻塞队列ConcurrentLinkedQueue的入队、出队做了一个简要的分析,本文将对并发包中的阻塞队列做一个简要分析。

  Java并发包中的阻塞队列一共7个,当然他们都是线程安全的。

  ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。

  LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。

  PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

  DealyQueue:一个使用优先级队列实现的无界阻塞队列。

  SynchronousQueue:一个不存储元素的阻塞队列。

  LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。

  LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。(摘自《Java并发编程的艺术》)

  在本文对ArrayBlockingQueue阻塞队列做一个简要解析

  对于ArrayLinkedQueue,放眼看过去其安全性的保证是由ReentrantLock保证的,有关ReentrantLock的解析可参考5.Lock接口及其实现ReentrantLock,在下文我也会适当的提及。

  首先来查看其构造函数:

构造方法

public ArrayBlockingQueue(int capacity)

构造指定大小的有界队列

public ArrayBlockingQueue(int capacity, boolean fair)

构造指定大小的有界队列,指定为公平或非公平锁

public ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)

构造指定大小的有界队列,指定为公平或非公平锁,指定在初始化时加入一个集合

 1 public ArrayBlockingQueue(int capacity) {
2   this(capacity, false);//默认构造非公平锁的阻塞队列
3 }
4 public ArrayBlockingQueue(int capacity, boolean fair) {
5   if (capacity <= 0)
6     throw new IllegalArgumentException();
7   this.items = new Object[capacity];
8   lock = new ReentrantLock(fair);//初始化ReentrantLock重入锁,出队入队拥有这同一个锁
9   notEmpty = lock.newCondition;//初始化非空等待队列,有关Condition可参考《6.类似Object监视器方法的Condition接口》
10   notFull = lock.newCondition;//初始化非满等待队列
11 }
12 public ArrayBlockingQueue(int capacity, boolean fair, Collecation<? extends E> c) {
13   this(capacity, fair);
14   final ReentrantLock lock = this.lock;
15   lock.lock();//注意在这个地方需要获得锁,这为什么需要获取锁的操作呢?
16   try {
17     int i = 0;
18     try {
19       for (E e : c) {
20         checkNotNull(e);
21         item[i++] = e;//将集合添加进数组构成的队列中
22       }
23     } catch (ArrayIndexOutOfBoundsException ex) {
24       throw new IllegalArgumentException();
25     }
26     count = i;//队列中的实际数据数量
27     putIndex = (i == capacity) ? 0 : i;
28   } finally {
29     lock.unlock();
30   }
31 }

  在第15行,源码里给了一句注释: Lock only for visibility, not mutual exclusion。这句话的意思就是给出,这个锁的操作并不是为了互斥操作,而是保证其可见性。线程T1是实例化ArrayBlockingQueue对象,T2是对实例化的ArrayBlockingQueue对象做入队操作(当然要保证T1和T2的执行顺序),如果不对它进行加锁操作(加锁会保证其可见性,也就是写回主存),T1的集合c有可能只存在T1线程维护的缓存中,并没有写回主存,T2中实例化的ArrayBlockingQueue维护的缓存以及主存中并没有集合c,此时就因为可见性造成数据不一致的情况,引发线程安全问题。

  以下是ArrayBlockingQueue的一些出队入队操作。

队列元素的插入

抛出异常

返回值(非阻塞)

一定时间内返回值

返回值(阻塞)

插入

add(e)//队列未满时,返回true;队列满则抛出IllegalStateException(“Queue full”)异常——AbstractQueue

offer(e)//队列未满时,返回true;队列满时返回false。非阻塞立即返回。

offer(e, time, unit)//设定等待的时间,如果在指定时间内还不能往队列中插入数据则返回false,插入成功返回true。

put(e)//队列未满时,直接插入没有返回值;队列满时会阻塞等待,一直等到队列未满时再插入。

//ArrayBlockingQueue#add
public boolean add(E e) {
  return super.add(e);
}
//AbstractQueue#add,这是一个模板方法,只定义add入队算法骨架,成功时返回true,失败时抛出IllegalStateException异常,具体offer实现交给子类实现。
public boolean add(E e) {
  if (offer(e))//offer方法由Queue接口定义
    return true;
  else
    throw new IllegalStateException();
}
//ArrayBlockingQueue#offer,队列未满时返回true,满时返回false
public boolean offer(E e) {
  checkNotNull(e);//检查入队元素是否为空
  final ReentrantLock lock = this.lock;
  lock.lock();//获得锁,线程安全
  try {
    if (count == items.length)//队列满时,不阻塞等待,直接返回false
      return false;
    else {
      insert(e);//队列未满,直接插入
      return true;
    }
  } finally {
    lock.unlock();
  }
}
//ArrayBlockingQueue#insert
private void insert(E e) {
  items[putIndex] = x;
  putIndex = inc(putIndex);
  ++count;
  notEmpty.signal();//唤醒非空等待队列中的线程,有关Condition可参考《6.类似Object监视器方法的Condition接口》
 }

  在这里有几个ArrayBlockingQueue成员变量。items即队列的数组引用,putIndex表示等待插入的数组下标位置。当items[putIndex] = x将新元素插入队列中后,调用inc将数组下标向后移动,如果队列满则将putIndex置为0:

//ArrayBlockingQueue#inc
private int inc(int i) {
  return (++i == items.length) ? 0 : i;
}

  接着解析下put方法,阻塞插入队列,当队列满时不会返回false,也不会抛出异常,而是一直阻塞等待,直到有空位可插入,但它可被中断返回。

//ArrayBlockingQueue#put
public void put(E e) throws InterruptedException {
  checkNotNull(e);//同样检查插入元素是否为空
  final ReentrantLock lock = this.lock;
  lock.lockInterruptibly();//这里并没有调用lock方法,而是调用了可被中断的lockInterruptibly,该方法可被线程中断返回,lock不能被中断返回。
  try {
    while (count == items.length)
      notFull.await();//当队列满时,使非满等待队列休眠
    insert(e);//此时表示队列非满,故插入元素,同时在该方法里唤醒非空等待队列
  } finally {
    lock.unlock();
  }
}

队列元素的删除 

抛出异常

返回值(非阻塞)

一定时间内返回值

返回值(阻塞)

remove()//队列不为空时,返回队首值并移除;队列为空时抛出NoSuchElementException()异常——AbstractQueue

poll()//队列不为空时返回队首值并移除;队列为空时返回null。非阻塞立即返回。

poll(time, unit)//设定等待的时间,如果在指定时间内队列还未孔则返回null,不为空则返回队首值

take(e)//队列不为空返回队首值并移除;当队列为空时会阻塞等待,一直等到队列不为空时再返回队首值。

//AbstractQueue#remove,这也是一个模板方法,定义删除队列元素的算法骨架,队列中元素时返回具体元素,元素为空时抛出异常,具体实现poll由子类实现,
public E remove() {
  E x = poll();//poll方法由Queue接口定义
  if (x != null)
    return x;
  else
    throw new NoSuchElementException();
}
//ArrayBlockingQueue#poll,队列中有元素时返回元素,不为空时返回null
public E poll() {
  final ReentrantLock lock = this.lock;
  lock.lock();
  try {
    return (count == 0) ? null : extract();
  } finally {
    lock.unlock();
  }
}
//ArrayBlockingQueue#extract
private E extract() {
  final Object[] items = this.items;
  E x = this.<E>cast(items[takeIndex]);//移除队首元素
  items[takeIndex] = null;//将队列数组中的第一个元素置为null,便于GC回收
  takeIndex = inc(takeIndex);
  --count;
  notFull.signal();//唤醒非满等待队列线程
  return x;
}

  对比add和offer方法,理解了上两个方法后remove和poll实际不难理解,同理在理解了put阻塞插入队列后,对比take阻塞删除队列元素同样也很好理解。

//ArrayBlockQueue#take
public E take() throws InterruptedException {
  final ReentrantLock lock = this.lock();
  lock.lockInterrupted();//这里并没有调用lock方法,而是调用了可被中断的lockInterruptibly,该方法可被线程中断返回,lock不能被中断返回。
  try {
    while (count == 0)//队列元素为空
      notEmpty.await();//非空等待队列休眠
    return extract();//此时表示队列非空,故删除元素,同时在里唤醒非满等待队列
  } finally {
    lock.unlock();
  }
}

  最后一个方法size。

public int size() {
  final ReentrantLock lock = this.lock;
  lock.lock();
  try {
    return count;
  } finally {
    lock.unlock();
  }
}

  可以看到ArrayBlockingQueue队列的size方法,是直接返回的count变量,它不像ConcurrentLinkedQueue,ConcurrentLinkedQueue的size则是每次会遍历这个队列,故ArrayBlockingQueue的size方法比ConcurrentLinkedQueue的size方法效率高。而且ConcurrentLinkedQueue的size方法并没有加锁!也就是说很有可能其size并不准确,这在它的注释中说明了ConcurrentLinkedQueue的size并没有多大的用处。

不积跬步,无以至千里;不积小流,无以成江海。
 

并发包阻塞队列之ArrayBlockingQueue的更多相关文章

  1. 10.并发包阻塞队列之ArrayBlockingQueue

    上一节中对并发包中的非阻塞队列ConcurrentLinkedQueue的入队.出队做了一个简要的分析,本文将对并发包中的阻塞队列做一个简要分析. Java并发包中的阻塞队列一共7个,当然他们都是线程 ...

  2. 11.并发包阻塞队列之LinkedBlockingQueue

    在上文<10.并发包阻塞队列之ArrayBlockingQueue>中简要解析了ArrayBlockingQueue部分源码,在本文中同样要介绍的是Java并发包中的阻塞队列LinkedB ...

  3. Java并发之BlockingQueue 阻塞队列(ArrayBlockingQueue、LinkedBlockingQueue、DelayQueue、PriorityBlockingQueue、SynchronousQueue)

    package com.thread.test.thread; import java.util.Random; import java.util.concurrent.*; /** * Create ...

  4. java并发包——阻塞队列BlockingQueue及源码分析

    一.摘要 BlockingQueue通常用于一个线程在生产对象,而另外一个线程在消费这些对象的场景,例如在线程池中,当运行的线程数目大于核心的线程数目时候,经常就会把新来的线程对象放到Blocking ...

  5. java阻塞队列之ArrayBlockingQueue

    在Java的java.util.concurrent包中定义了和多线程并发相关的操作,有许多好用的工具类,今天就来看下阻塞队列.阻塞队列很好的解决了多线程中数据的安全传输问题,其中最典型的例子就是客园 ...

  6. 9.并发包非阻塞队列ConcurrentLinkedQueue

    jdk1.7.0_79  队列是一种非常常用的数据结构,一进一出,先进先出. 在Java并发包中提供了两种类型的队列,非阻塞队列与阻塞队列,当然它们都是线程安全的,无需担心在多线程并发环境所带来的不可 ...

  7. 阻塞队列---ArrayBlockingQueue,LinkedBlockingQueue,DelayQueue源码分析

    阻塞队列和非阻塞队列阻塞队列和非阻塞队列的区别:阻塞队列可以自己阻塞,非阻塞队列不能自己阻塞,只能使用队列wait(),notify()进行队列消息传送.而阻塞队列当队列里面没有值时,会阻塞直到有值输 ...

  8. Java中的阻塞队列-ArrayBlockingQueue(一)

    最近在看一些java基础的东西,看到了队列这章,打算对复习的一些知识点做一个笔记,也算是对自己思路的一个整理,本章先聊聊java中的阻塞队列 参考文章: http://ifeve.com/java-b ...

  9. 阻塞队列 BlockingQueue 详解

    转自:https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487078&idx=2&sn=315f39b6d53 ...

随机推荐

  1. 问题:sqlserver if;结果: SqlServer if else和case

    SqlServer if else和case 分类: SQLSERVER 2013-03-01 16:51 11328人阅读 评论(0) 收藏 举报 行转列 目录(?)[+] if else 要提示的 ...

  2. SpringMVC 学习笔记(请求方法的返回值和参数)

    在用注解对配置 处理器时,一般是一个方法处理一个请求,不同方法的返回类型有着不同的意义. 返回值为 ModelAndView 类型 ModelAndView 是Model 和 View 的一个集合类型 ...

  3. PopupWindow 从底部弹出窗体

    第一步  : 初始化PopupWindow private void initPop() { if (view == null) { // 照片 view = View.inflate(Registe ...

  4. Activity---弹出右侧窗口

    第一步: Activity弹出窗口的布局 <?xml version="1.0" encoding="UTF-8"?> //布局文件main_top ...

  5. 全文检索技术---solr

    1       Solr介绍 1.1   什么是solr Solr 是Apache下的一个顶级开源项目,采用Java开发,它是基于Lucene的全文搜索服务器.Solr可以独立运行在Jetty.Tom ...

  6. 使用python已知平均数求随机数

    问题描述:产生40个数,范围是363-429之间,平均值为402 思路: 1 产生一个随机数 2 使用平均数减去随机数求出第二个数,生成20组 3 将排序打乱 # -*- coding: cp936 ...

  7. 关于 char 和 unsigned char 的区别

    首先卖个关子: 为什么网络编程中的字符定义一般都为无符号的字符?   char buf[16] = {0}; unsigned char ubuf[16] = { 0 };   上面两个定义的区别是: ...

  8. latex 输入矩阵

    %以下函数可以写矩阵 %这个是放括号的矩阵 $A=\begin{bmatrix} 1&3\\ 3&3 \end{bmatrix}$ %这个是圆括号的矩阵 $A=\begin{pmatr ...

  9. 基于FPGA具有容错能理的异步串口程序设计

    首先,问题源于一个项目.本来是一个很简单的多个串口收发FIFO存取数据的小程序,通过电脑验证也可用,而下位机板子之间通信就出现了丢数问题. 经过分析原因如下: 我的串口收模块是基于特权同学的开发板程序 ...

  10. PLSQL Developer 直接用ip访问指定数据库