java多线程-BlockingQueue实现的生产者消费之模型

阻塞队列是一个支持两个附加操作的队列,一个是支持阻塞的插入操作,另一个是支持阻塞的删除操作
支持阻塞的插入操作:当队列满时,队列会阻塞插入元素的线程,直到队列不满
支持阻塞的移除操作:当队列为空时,队列会阻塞删除元素的线程,直到线程中存在元素
这里写图片描述
还有一些其他的类,没有详细列出来

一、ArrayBlockingQueue

public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    /**
     * Serialization ID. This class relies on default serialization
     * even for the items array, which is default-serialized, even if
     * it is empty. Otherwise it could not be declared final, which is
     * necessary here.
     */
    private static final long serialVersionUID = -817911632652898426L;

    /** queue用数组表示*/
    final Object[] items;

    /** 从queue中取出下一个元素的位置*/
    int takeIndex;

    /** 下一个item要插入的位置*/
    int putIndex;

    /** 队列中item的数量*/
    int count;

    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */

    /** 一个重入锁用于同步 */
    final ReentrantLock lock;

    /** 等待从queue中取元素的Condition */
    private final Condition notEmpty;

    /** 等待从queue中添加元素的Condition */
    private final Condition notFull;
}
put方法:将item添加到队列中
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)//如果数组已满,那么就阻塞,并释放锁,并进入Waiting状态
                notFull.await();
            final Object[] items = this.items;//如果有线程从其中取了元素,那么就会唤醒这个线程继续执行
            items[putIndex] = x;
            if (++putIndex == items.length)
                putIndex = 0;
            count++;
            notEmpty.signal();//执行完以后,需要唤醒那些正在向数组中获取元素的线程,继续执行
        } finally {
            lock.unlock();
        }
    }
take方法:将item从队列中添加进来
public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)//如果数组为空,那么就将获取元素的线程进入等待,让其他添加元素的线程去执行
                notEmpty.await();
            E x = (E) items[takeIndex];
            items[takeIndex] = null;
            if (++takeIndex == items.length)
                takeIndex = 0;
            count--;
            if (itrs != null)
                itrs.elementDequeued();
            notFull.signal();//因为获取线程的元素已执行完毕,需要去唤醒那些因为数组已满而导致不能执行添加的线程
            return x;
        } finally {
            lock.unlock();
        }
    }

二、LinkedBlockingQueue

参数详情

public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {
    private static final long serialVersionUID = -6903933977591709194L;
    静态内部类用于存储节点
    static class Node<E> {
        E item;

        Node<E> next;

        Node(E x) { item = x; }
    }

    /** 如果我们不指明队列的长度,那么队列是一个无限长的队列 */
    private final int capacity;

    /** 链表中的元素 */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * 链表的头,head.item = null
     */
    transient Node<E> head;

    /**
     * 链表的尾节点,不变式last.next= null
     */
    private transient Node<E> last;

    /** 从链表中取元素需要获得takeLock锁 */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** Wait queue for waiting takes */
    private final Condition notEmpty = takeLock.newCondition();

    /** 向链表中添加插入元素需要获得putLock锁*/
    private final ReentrantLock putLock = new ReentrantLock();

    /** Wait queue for waiting puts */
    private final Condition notFull = putLock.newCondition();
}
put操作

从队列中添加元素,如果队列已满,那么将一直等待;一旦其他线程从队列中获取了元素,则会唤醒等待添加元素的线程

    public void put(E e) throws InterruptedException {
        if (e == null) throw new NullPointerException();
        int c = -1;
        Node<E> node = new Node<E>(e);
        final ReentrantLock putLock = this.putLock;
        final AtomicInteger count = this.count;
        putLock.lockInterruptibly();
        try {
            while (count.get() == capacity) {//如果队列已满,那么释放锁,进入等待区
                notFull.await();
            }
            enqueue(node);
            c = count.getAndIncrement();
            if (c + 1 < capacity)//如果添加元素以后发现容量并没有满,那么就可以唤醒其他线程继续添加元素
                notFull.signal();
        } finally {
            putLock.unlock();
        }
        if (c == 0)//如果队列不为空,那么这个时候唤醒获取元素的线程,但是
            signalNotEmpty();
    }
    private void signalNotEmpty() {
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lock();
        try {
            notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
    }
    private void enqueue(Node<E> node) {
        last = last.next = node;//保证last永远指向最后一个Node
    }

take操作

从队列中获取元素,如果队列为空,那么将一直等待;一旦其他线程向队列中添加了元素,则会唤醒等待获取元素的线程

    public E take() throws InterruptedException {
        E x;
        int c = -1;
        final AtomicInteger count = this.count;
        final ReentrantLock takeLock = this.takeLock;
        takeLock.lockInterruptibly();
        try {
            while (count.get() == 0) {//如果
                notEmpty.await();
            }
            x = dequeue();
            c = count.getAndDecrement();
            if (c > 1)
                notEmpty.signal();
        } finally {
            takeLock.unlock();
        }
        if (c == capacity)
            signalNotFull();
        return x;
    }
    private void signalNotFull() {
        final ReentrantLock putLock = this.putLock;
        putLock.lock();
        try {
            notFull.signal();
        } finally {
            putLock.unlock();
        }
    }
    private E dequeue() {
        Node<E> h = head;
        Node<E> first = h.next;
        h.next = h; // help GC
        head = first;
        E x = first.item;
        first.item = null;
        return x;
    }

ArrayBlockingQueue和LinkedBlockingQueue比较

1.从队列容量角度看:队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。

2.从数据结构看:数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。

3.由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响。

4.从锁实现的方式看:两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值