前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解阻塞队列

深入理解阻塞队列

作者头像
Java识堂
发布2019-08-13 10:02:02
2300
发布2019-08-13 10:02:02
举报
文章被收录于专栏:Java识堂

前言

建议先看一下这篇分享,深入理解Condition

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。

  1. 支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入的元素,直到队列不满
  2. 支持阻塞的移除方法:意思是在队列为空时,获取元素的线程会等待队列变为非空

阻塞队列常用于生产者和消费者的场景,生产者是向队列里添加元素的线程,消费者从队列里取元素的线程。阻塞队列就是生产者用来存放元素,消费者用来获取元素的容器

方法/处理方式

抛出异常

返回特殊值

插入方法

boolean add(c)

boolean offer(e)

解释

添加元素,添加成功返回true,如果队列满了,抛出IllegalStateException

添加元素,添加成功返回true,如果队列满了,返回false

移除方法

E remove()

E poll()

解释

返回头结点,从队列中移除头节点,如果队列为空,抛出NoSuchElementException

返回头结点,从队列中移除头节点,如果队列为空,返回null

检查方法

E element()

E peek()

解释

返回头结点,但是不从队列中移除头节点,如果队列为空,抛出NoSuchElementException

返回头结点,但是不从队列中移除头节点,如果队列为空,返回null

方法/处理方式

一直阻塞

超时退出

插入方法

void put(e)

boolean offer(e, time, unit)

解释

添加元素,如果队列已经满了,则阻塞等待

添加元素,添加成功返回true,如果队列已经满了,则阻塞等待,指定时间已经过去还没能添加成功元素,则返回false

移除方法

E take()

E poll(time, unit)

解释

返回头结点,从队列中移除头节点,如果队列为空则阻塞等待

返回头结点,从队列中移除头节点,队列中没元素会一直阻塞等待,指定时间已经过去还没能拿到头节点,则返回null

检查方法

不可用

不可用

例子

举一个多生产者,多消费者的例子,队列的大小为3,即队列大小为3时,生产者就不再生产

代码语言:javascript
复制
public class Producer implements Runnable {

    private ArrayList list;

    private int capacity;

    public Producer(ArrayList queue,int capacity) {
        this.list = queue;
        this.capacity = capacity;
    }

    @Override
    public void run() {
        synchronized (list) {
            while (true) {
                while (list.size() == capacity) {
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Object object = list.add(new Object());
                System.out.println(Thread.currentThread().getName() + " 生产");
                list.notifyAll();
            }
        }
    }
}

注意消费者和生产者都是用的notifyAll()[通知所有阻塞的线程]方法,而不是notify()[通知一个阻塞的线程]方法,因为有可能出现“生产者”唤醒“生产者”,消费者“唤醒”消费者的情况,因此有可能造成死锁,这里以1个消费者,3个生产者为例说一下,消费者1获得锁还没产品呢,阻塞,接着生产者1获得锁生产完了,然后生产者2获得锁后生产完了,再然后生产者3获得锁生产完了,最后生产者1获得锁了,然后阻塞了,现在好了生产者和消费者都阻塞了,造成了死锁。notifyAll()则不会造成死锁,接着上面的步骤,生产者3生产完了释放锁后,会通知所有阻塞的线程,因此消费者1肯定有机会拿到锁来进行消费

代码语言:javascript
复制
public class Consumer implements Runnable {

    private List list;

    public Consumer(List queue) {
        this.list = queue;
    }

    @Override
    public void run() {
        synchronized (list) {
            while (true) {
                while (list.size() == 0) {
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Object object = list.remove(0);
                System.out.println(Thread.currentThread().getName() + " 消费");
                list.notifyAll();
            }
        }
    }
}
代码语言:javascript
复制
public class Test {

    public static void main(String[] args) {

        ArrayList list = new ArrayList(3);
        for (int i = 0; i < 3; i++) {
            new Thread(new Producer(list, 3), "生产者" + i).start();
            new Thread(new Consumer(list), "消费者" + i).start();
        }
    }
}

一部分结果

代码语言:javascript
复制
生产者0 生产
生产者0 生产
生产者0 生产
消费者1 消费
消费者1 消费
消费者1 消费
生产者1 生产

把这个实例用阻塞队列来改写,先自己写一个阻塞队列,实现BlockingQueue接口,这里只展示了一部分重写的方法

代码语言:javascript
复制
public class MyBlockingQueue<E> implements BlockingQueue<E> {

    private int capacity;
    private List<E> list;

    public MyBlockingQueue(int capacity) {
        this.capacity = capacity;
        this.list = new ArrayList(capacity);
    }

    @Override
    public void put(E e) throws InterruptedException {
        synchronized (this) {
            if (list.size() == capacity) {
                this.wait();
            }
            list.add(e);
            this.notifyAll();
        }
    }

    @Override
    public E take() throws InterruptedException {
        synchronized (this) {
            while (list.size() == 0) {
                this.wait();
            }
            E value = list.remove(0);
            this.notifyAll();
            return value;
        }
    }

}
代码语言:javascript
复制
public class Producer implements Runnable {

    private BlockingQueue queue;

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

    @Override
    public void run() {
        while (true) {
            try {
                queue.put(new Object());
                System.out.println(Thread.currentThread().getName() + " 生产");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
代码语言:javascript
复制
public class Consumer implements Runnable {

    private BlockingQueue queue;

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

    @Override
    public void run() {
        while (true) {
            try {
                Object object = queue.take();
                System.out.println(Thread.currentThread().getName() + " 消费");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
代码语言:javascript
复制
public class Test {

    public static void main(String[] args) {
        BlockingQueue queue = new MyBlockingQueue(3);
        for (int i = 0; i < 3; i++) {
            new Thread(new Producer(queue), "生产者" + i).start();
            new Thread(new Consumer(queue), "消费者" + i).start();
        }
    }
}

这里将BlockingQueue的实现改为ArrayBlockingQueue,程序运行结果一样,和我们之前的例子比较,BlockingQueue其实就是不用我们自己写阻塞和唤醒的部分,直接看一下ArrayBlockingQueue的源码,其实和我自己实现的差不多,只不过并发这部分源码用的是ReentLock,而我用的是synchronized

源码

基于jdk1.8.0_20

先看属性

代码语言:javascript
复制
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    /** The queued items */
    final Object[] items;

    /** items index for next take, poll, peek or remove */
    int takeIndex;

    /** items index for next put, offer, or add */
    int putIndex;

    /** Number of elements in the queue */
    int count;

    /** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;

}

构造函数

代码语言:javascript
复制
// 设置同步队列的大小和锁是否公平
public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
        throw new IllegalArgumentException();
    this.items = new Object[capacity];
    lock = new ReentrantLock(fair);
    notEmpty = lock.newCondition();
    notFull =  lock.newCondition();
}

put方法

代码语言:javascript
复制
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    // 响应中断的方式上锁
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            // 将当前线程加入notFull这个等待队列
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}
代码语言:javascript
复制
private static void checkNotNull(Object v) {
    if (v == null)
        throw new NullPointerException();
}
代码语言:javascript
复制
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();
}

take方法

代码语言:javascript
复制
public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            // 将当前线程加入notEmpty这个等待队列
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}
代码语言:javascript
复制
private E dequeue() {
    // assert lock.getHoldCount() == 1;
    // assert items[takeIndex] != null;
    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)
        //更新iterators,不再分析
        itrs.elementDequeued();
    notFull.signal();
    return x;
}

其他方法就不再分析,基本上就是上锁,然后进行相应的操作

最后说一下LZ的理解,个人感觉用ArrayBlockingQueue实现生产者和消费者,比我上面用synchronized的方式应该快很多,毕竟ArrayBlockingQueue只会是生成者通知消费者,或者消费者通知生产者,而synchronized不是,会造成很多不必要的锁竞争,当然并没有实验,有时间可以试试

参考书籍

《Java并发编程的艺术》

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java识堂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 例子
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档