1. 什么是阻塞队列?

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。

这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。

当队列满时,存储元素的线程会等待队列可用。

阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。

阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

阻塞队列提供了四种处理方法:

add        增加一个元索                     如果队列已满,则抛出一个IIIegaISlabEepeplian异常
remove   移除并返回队列头部的元素    如果队列为空,则抛出一个NoSuchElementException异常
element  返回队列头部的元素             如果队列为空,则抛出一个NoSuchElementException异常
offer       添加一个元素并返回true       如果队列已满,则返回false
poll         移除并返问队列头部的元素    如果队列为空,则返回null
peek       返回队列头部的元素             如果队列为空,则返回null
put         添加一个元素                      如果队列满,则阻塞
take        移除并返回队列头部的元素     如果队列为空,则阻塞

remove、element、offer 、poll、peek 其实是属于Queue接口。

方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用

2. Java里的阻塞队列

JDK7提供了7个阻塞队列。分别是

  • ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列,其构造函数必须带一个int参数来指明其大小。
  • LinkedBlockingQueue :若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制。若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定。
  • PriorityBlockingQueue :一个支持优先级排序无界阻塞队列,其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序。
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列,本身不存在容量
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列,当前存在一个正在等待获取的消费者线程,即立刻移交之;否则,会插入当前元素e到队列尾部,并且等待进入阻塞状态,到有消费者线程取走该元素。。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

上点源码 就更明白了。以ArrayBlockingQueue类为例:

先看阻塞的方法,这ReentrantLock的使用方式就能说明这个类是线程安全类
public boolean offer(E e) {
        if (e == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length)//队列已满,返回false
                return false;
            else {
                insert(e);
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == 0)//队列为空,返回false
                return false;
            Else{

                x = extract();
                return true;
           }
        } finally {
            lock.unlock();
        }
    }
 
 
再看阻塞的方法,这里面涉及到Condition类,简要提一下, 
await方法指:造成当前线程在接到信号或被中断之前一直处于等待状态。 
signal方法指:唤醒一个等待线程。
用一个ReentrantLock ,两个Condition。实现了阻塞算法。

private final Condition notFull;
private final Condition notEmpty;
public ArrayBlockingQueue(int capacity, boolean fair) {
//省略其他代码
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}

public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        final E[] items = this.items;
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            try {
                while (count == items.length)//如果队列已满,等待notFull这个条件,这时当前线程被阻塞
                    notFull.await();
            } catch (InterruptedException ie) {
                notFull.signal(); //唤醒受notFull阻塞的当前线程
                throw ie;
            }
            insert(e);//insert方法里notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            try {
                while (count == 0)//如果队列为空,等待notEmpty这个条件,这时当前线程被阻塞
                    notEmpty.await();
            } catch (InterruptedException ie) {
                notEmpty.signal();//唤醒受notEmpty阻塞的当前线程
                throw ie;
            }
            return extract();//extract方法里notFull.signal();
        } finally {
            lock.unlock();
        }
    }

 
 
LinkedBlockingQueue相对于ArrayBlockingQueue还有不同的是,
有两个ReentrantLock,且队列现有元素的大小由一个AtomicInteger对象标示。 
有两个Condition很好理解,在ArrayBlockingQueue也是这样做的。但是为什么需要两个ReentrantLock呢?
 
入队操作其实操作的只有队尾引用last,并且没有牵涉到head。
而出队操作其实只针对head,和last没有关系。
那么就 是说入队和出队的操作完全不需要公用一把锁,所以就设计了两个锁,这样就实现了多个不同任务的线程入队的同时可以进行出队的操作,
锁分段技术提高性能,如ConcurrentHashMap性能优于HashTable。
 
另一方面由于两个操作所 共同使用的count是AtomicInteger类型的,所以完全不用考虑计数器递增递减的问题。

深入理解java:2.3.5. 并发编程concurrent包 之容器BlockingQueue(阻塞队列)的更多相关文章

  1. 深入理解java:2.3.4. 并发编程concurrent包 之容器ConcurrentLinkedQueue(非阻塞的并发队列---循环CAS)

    1.    引言 在并发编程中我们有时候需要使用线程安全的队列. 如果我们要实现一个线程安全的队列有两种实现方式:一种是使用阻塞算法,另一种是使用非阻塞算法. 使用阻塞算法的队列可以用一个锁(入队和出 ...

  2. 深入理解java:2.3.3. 并发编程concurrent包 之容器ConcurrentHashMap

    线程不安全的HashMap 因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap. 效率低下的HashTable容器 H ...

  3. 并发编程学习笔记(13)----ConcurrentLinkedQueue(非阻塞队列)和BlockingQueue(阻塞队列)原理

    · 在并发编程中,我们有时候会需要使用到线程安全的队列,而在Java中如果我们需要实现队列可以有两种方式,一种是阻塞式队列.另一种是非阻塞式的队列,阻塞式队列采用锁来实现,而非阻塞式队列则是采用cas ...

  4. Python并发编程-concurrent包

    Python并发编程-concurrent包 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.concurrent.futures包概述 3.2版本引入的模块. 异步并行任务编程 ...

  5. 深入理解java:2.3. 并发编程 java.util.concurrent包

    JUC java.util.concurrent包, 这个包是从JDK1.5开始引入的,在此之前,这个包独立存在着,它是由Doug Lea开发的,名字叫backport-util-concurrent ...

  6. 深入理解java:2.3.6. 并发编程concurrent包 之管理类---线程池

    我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁 ...

  7. 深入理解java:2.3.2. 并发编程concurrent包 之重入锁/读写锁/条件锁

    重入锁 Java中的重入锁(即ReentrantLock)   与JVM内置锁(即synchronized)一样,是一种排它锁. ReentrantLock提供了多样化的同步,比如有时间限制的同步(定 ...

  8. 深入理解java:2.3.1. 并发编程concurrent包 之Atomic原子操作(循环CAS)

    java中,可能有一些场景,操作非常简单,但是容易存在并发问题,比如i++, 此时,如果依赖锁机制,可能带来性能损耗等问题, 于是,如何更加简单的实现原子性操作,就成为java中需要面对的一个问题. ...

  9. java并发编程笔记3-同步容器&并发容器&闭锁&栅栏&信号量

    一.同步容器: 1.Vector容器实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施.保证了线程 ...

随机推荐

  1. django前戏

    Django前戏: 1.软件开发: C/S 客户端与服务端 HTTP(超文本传输协议):协议的由来,如同sql语句由来一样.为了开发使用方便所形成的统一接口统一规范 学习Django之前我们先来了解下 ...

  2. 用Python实现简单购物车

    作业二:简单购物车# 实现打印商品详细信息,用户输入商品名和购买个数,则将商品名,价格,购买个数加入购物列表,# 如果输入为空或其他非法输入则要求用户重新输入 shopping_list = [] w ...

  3. 严重: Servlet [SelectController] in web application [/servlet4] threw load() exception

    在web.xml路径配置.jar包导入都正确的情况下,那就考虑是环境问题. 1.servers-->clean 将代码从tomcat中清除 2.Project-->clean  将ecli ...

  4. springMVC中使用 JSR-303验证器( Validation 接口 )

    在pom.xml,添加validator验证器的依赖 <dependency> <groupId>org.hibernate</groupId> <artif ...

  5. Python可迭代序列反转总结

    字符串反转 示例:s = "hello" 方法一:使用切片 def reversed_str(s): return s[::-1] 方法二:使用reversed # 字符串 -&g ...

  6. C# 扩展方法——序列化与反序列化

    其他扩展方法详见:https://www.cnblogs.com/zhuanjiao/p/12060937.html 主要是是对日期格式的处理 using Newtonsoft.Json; using ...

  7. QT Creator 添加Lib库

    LIBS += -LD:/Path/ -lTest1.lib -lTest2.lib 解析:在路径D:/Path/ 下 添加Test1.lib Test2.lib库 -L 和小写'-l"没有 ...

  8. Ajax异步传值总结

    Ajax异步传值 将数据从前台传向后台: 1:通过get方式,将参数在链接中,配合“?”进行传值. 实例: //前台传值方法 //触发该方法调用ajax function testAjax(yourD ...

  9. QT5 Even 事件

    事件的引入: 实现功能: 1.点击button 文本框两字改变成button被按下;很简单的在button上转到槽对lineEdit->setTest()设置即可; void myWidget: ...

  10. JDK_API剖析之java.io包

    Java的核心库java.io提供了全面的IO接口.包括:文件读写.标准设备输出等.Java中IO是以流为基础进行输入输出的,所有数据被串行化写入输出流,或者从输入流读入. 一.接口 1.Closea ...