JUC-BlockingQueue

一、简介

BlockingQueue是juc包下提供的一种队列工具,被称作阻塞队列,他的工作模式类似于生产者和消费者的工作过程。在juc的线程池中使用到,做为存储任务的队列。内部使用了Lock锁和Condition条件参数作为阻塞功能实现的基础,可以在文章《JUC-Lock工具解析》中简单了解Lock的实现方式,从而能更高的理解BlockingQueue的实现原理。

二、使用

BlockingQueue是一个很经典的生产者-消费者类型的队列模式,生产者不断的向队列中生产输入,如果队列满了则会阻塞生产者直到队列不满;另一方面消费者则不断的从队列中获取消费,如果队列是空的,则会阻塞消费者,直到队列不为空。下面是一个ArrayBlockingQueue使用的简单的例子,可以大致的了解一下他的工作方式:

public class BlockingQueueTest {
    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(512);
        Producer producer = new Producer(queue);
        Consumer consumer1 = new Consumer(queue);
        Consumer consumer2 = new Consumer(queue);
        new Thread(producer).start();
        new Thread(consumer1).start();
        new Thread(consumer2).start();
    }
}

class Producer implements Runnable {
    BlockingQueue queue;

    @Override
    public void run() {
        while (true) {
            try {
                queue.put(produce());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    Producer(BlockingQueue q) {
        this.queue = q;
    }

    Object produce() {
        Object o = new Object();
        System.out.println("producer is produce " + " " + o.hashCode());
        return o;
    }
}

class Consumer implements Runnable {
    BlockingQueue queue;

    @Override
    public void run() {
        while (true) {
            try {
                consume(queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    Consumer(BlockingQueue q) {
        this.queue = q;
    }

    void consume(Object o) {
        System.out.println("consumer is consume " + " " + o.hashCode());
    }
}

在官方的介绍上,也有一个关于BlockIngQueue的使用实例,他使用的是伪代码的方式,是以BlockingQueue接口为题展示的,更能显示BlockingQueue的通用特性。

三、解析

BlockingQueue是一个接口,他继承于集合类,因此具备集合类的操作属性,接口继承关系如下图所示:

 BlockingQueue作为接口形式存在,定义了一系列操作队列的方法,全部方法如下图所示:

    boolean add(E e);

    boolean offer(E e);

    void put(E e) throws InterruptedException;

    boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;

    E take() throws InterruptedException;

    E poll(long timeout, TimeUnit unit) throws InterruptedException;

    int remainingCapacity();

    boolean remove(Object o);

    public boolean contains(Object o);

    int drainTo(Collection<? super E> c);

    int drainTo(Collection<? super E> c, int maxElements);

方法性质:简单的描述这些方法的作用方式以及特点,如下图所示(部分方法是集合类下的操作方式)

操作类别抛异常特殊值阻塞超时
添加add()

offer()

put()offer(time)
获取remove()poll()take()poll(time)
校验element()peek()不支持不支持

1.抛异常

当操作没有得到立即执行的时候,抛出异常;

2.特殊值

当操作没有得到立即执行的时候,返还特定的值(比如true/false);

3.阻塞

当操作没有得到立即执行的时候,此方法会发生阻塞,直到满足条件能够执行;

4.超时

当操作没有得到立即执行的时候,此方法会发生阻塞,等待时间不会超出传入的规定时间,返回特定值(比如true/false);

队列特点:

BlockIngQueue:禁止使用null值,队列添加元素会对null值进行判断;

BlockIngQueue:无界和有界,比如数组队列需要指定队列大小,链表队列则可以不指定(Integer最大值);

BlockIngQueue:可以使用集合相关操作,继承于集合。比如remove()、forEach()等;

BlockIngQueue:是线程安全的,使用Lock锁;

主要实现:

ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、SynchronousQueue以及接口DelayQueue以及它的实现类LinkedBlockingDeque等。

队列队列特性
ArrayBlockingQueue(数组队列)数组实现,有界队列,FIFO
LinkedBlockingQueue(链表队列)链表实现,有界队列或无界(Integer最大值)
PriorityBlockingQueue(优先级队列)heap结构,默认按照自然排序,可以指定排序方式
SynchronousQueue(单元素队列)队列中最多只能有一个元素

ArrayBlockingQueue讲解:

下面我们看下ArrayBlockingQueue的实现逻辑,首先我们看下此类的成员变量工具及其相应的作用

    //底层数组
    final Object[] items;

    //下一次take,poll,peek,remove方法操作的数组元素下标
    int takeIndex;

    //下一次put,offer,add方法操作的数组元素下标
    int putIndex;

    //队列元素的数量
    int count;

    //队列操作的锁
    final ReentrantLock lock;

    //消费队列的Condition 
    private final Condition notEmpty;

    //生产队列的Condition
    private final Condition notFull;

    //迭代器
    transient Itrs itrs = null;

生产队列:

接下来我们看下入队的操作add()方法逻辑,源码如下:

    public boolean add(E e) {
        return super.add(e);//调用父类的add()方法
    }

可以看到调用的是抽象父类AbstractQueue的add()逻辑如下 :

    public boolean add(E e) {
        if (offer(e))//Queue接口定义的方法
            return true;
        else
            throw new IllegalStateException("Queue full");//队列满,则抛出异常
    }

父类的add()将会调用offer()方法,如果成功,则返回true;如果失败,则说明队列已经满了,直接抛出异常。队列工具ArrayBlockingQueue实现了此offer()方法,逻辑如下:

    public boolean offer(E e) {
        checkNotNull(e);//校验null
        final ReentrantLock lock = this.lock;
        lock.lock();//加锁
        try {
            if (count == items.length)//如果队列满了,返回false
                return false;
            else {
                enqueue(e);//如果队列不满,则执行入队方法,并返回true
                return true;
            }
        } finally {
            lock.unlock();
        }
    }

首先会有一个Lock加锁操作,然后判断队列是否是满的状态,根据维护的一个队列元素数量值和队列数组的length去比较,如果队列满了即二者相等,则返回fasle,add()方法会抛出异常;如果不满,则执行入队操作,进入方法enqueue()方法:

    private void enqueue(E x) {
        final Object[] items = this.items;
        items[putIndex] = x;//添加元素至入队元素下标处
        if (++putIndex == items.length)//如果队列满了,则将putIndex值设为0,重复使用
            putIndex = 0;
        count++;//维护队列元素值加一 
        notEmpty.signal();//通知消费者们,队列有元素
    }

下面再看下提供阻塞功能的put()方法逻辑:

    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();
        }
    }

 可以看到和offer()方法的逻辑基本上是相同的,不同之处在于对于队列满了之后的处理逻辑,offer()是直接返回false,而put()方法则是调用了await()方法,使得本线程阻塞在此处,并且释放了锁,其他线程也会阻塞在这个地方,等待消费队列的线程消费队列,唤醒阻塞的生产线程。

消费队列:

下面我们看下消费队列的方法,和生产队列的方法一样,是区分是否阻塞的,因为两种模式相差不多,所以放在一起查看,方便对比,逻辑如下:

    //非阻塞
    public E poll() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return (count == 0) ? null : dequeue();//队列空,则返回null
        } finally {
            lock.unlock();
        }
    }
    
    //阻塞功能
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();//队列空,则释放锁并且阻塞
            return dequeue();//队列不空,出队元素
        } finally {
            lock.unlock();
        }
    }

可以看到和生产队列的阻塞方式是一样的,通过调用Condition的await()方法。当队列空的时候就会对本线程进行阻塞,如果队列不为空的话,则会进入dequeue()方法,出队一个元素,逻辑如下:

    private E dequeue() {
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];//获取元素
        items[takeIndex] = null;//释放数组位置以及元素引用
        if (++takeIndex == items.length)
            takeIndex = 0;//如果
        count--;
        if (itrs != null)
            itrs.elementDequeued();//清楚元素后,要处理迭代器
        notFull.signal();
        return x;
    }

取出数组takeIndex下标处的值,然后释放此下标元素,看下出队小标位置是否等于数组长度,如果等于,则将takeIndex设为0,最后通知生产者,队列不满了,可以生产了。

总结:

1.通过构造函数创建ArrayBlockingQueue实例,指定队列的size;

2.生产者线程向队列中添加元素,如果队列元素数量已经达到指定size大小,则根据方法区分处理(异常、标识、阻塞);

3.消费者去队列中消费弹出元素,如果队列中无元素可消费,则根据方法区分处理(标识、阻塞);

以上是ArrayBlockingQueue的简单的源码简析,其他阻塞队列可以自行看下源码,尝试解析他们的工作过程。

四、资源地址

官网:http://www.java.com

文档:《Thinking in java》

jdk1.8版本源码

BlockingQueue官方文档:https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值