BlockingQueue

对于Queue而言,blockingqueue是主要的线程安全的版本,具有阻塞特征,可以允许添加、删除元素时被阻塞,直到成功为止,blockingqueue相对于Queue而言增加了两个方法put/take

BlockingQueue接口

属于并发容器的接口,在java,util.concurrent包路径下

BlockingQueue不接受null元素,尝试通过add/put、offer等添加一个null元素时,某些实现上会抛出nullpointExecption问题

BlockingQueue是可以指定容量,如果给定的数据超过指定容量,便无法添加元素,如果没有指定容量约束,最大大小是Integer.MAX_VALUE值

BlockingQueue实现类主要用于生产者-消费者队列,另外还支持Collection接口

BlockingQueue实现了线程安全,所有排队方法都可以使用内部锁或者其他并发控制形式来达到线程安全的目的

三个主要实现类介绍:

ArrayBlockingQueue:有界阻塞队列

LinkedBlockingQueue:无界阻塞队列

SynchronousQueue:同步队列

ArrayBlockingQueue:有界阻塞队列

ArrayBlockingQueue有界阻塞队列底层实现是数组,数组大小是固定的,假如数组一段为头,另一端为尾,那么头和尾构建一个FIFO队列(循环队列)

属性和默认值

    //存储的数据 存放在数组中
    final Object[] items;
    //读数据位置
    int takeIndex;
    //写入数据位置
    int putIndex;
    //数据数量
    int count;
    //队列同步相关属性
    final ReentrantLock lock;
    private final Condition notEmpty;
    private final Condition notFull;

通过ArrayBlockingQueue数据结构可知:首先是一个数组T[],用来存储所有的元素,由于ArrayBlockingQueue最终设置为一个不可扩展大小的Queue,所以这里items就是初始化就固定大小的数组(final),另外有两个索引,头索引takeIndex,尾索引putIndex,一个队列的大小count,要阻塞的话就必须用到一个锁和两个条件(非空,非满),这三个条件都是不可变类型

因为只有一把锁,所以任意时刻对队列只能有一个线程在执行,意味着索引和大小的操作都是线程安全的,所以可以看到takeIndex等就不需要原子操作和volatile语义了

构造函数:

 public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }

   //通过初始容量和是否公平性抢锁标志来进行实例化
    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();
    }

    //通过初始容量capacity、公平性标志fair和集合c
    public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c) {
        this(capacity, fair);

        final ReentrantLock lock = this.lock;
        lock.lock(); // Lock only for visibility, not mutual exclusion
        try {
            int i = 0;
            try {
                for (E e : c) {
                    //数据是不能为null
                    checkNotNull(e);
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException ex) {
                throw new IllegalArgumentException();
            }
            count = i;
            putIndex = (i == capacity) ? 0 : i;
        } finally {
            lock.unlock();
        }
    }

put操作:可阻塞的添加元素

 public void put(E e) throws InterruptedException {
        //检测插入数据不能为null
        checkNotNull(e);
        //添加可中断的锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length) //容量满了需要阻塞
                notFull.await();
            //当前集合未满,执行插入操作
            insert(e);
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    private void insert(E x) {
        items[putIndex] = x;
        putIndex = inc(putIndex);
        ++count;
        //通知take操作已经有数据吗,如果有take方法阻塞,此时可被唤醒来执行take操作
        notEmpty.signal();
    }

  //循环数组的特殊标志处理 ,如果是到最大值则重定向到0号索引
    final int inc(int i) {
        return (++i == items.length) ? 0 : i;
    }

take方法:将数据从队列中移除

    public E take() throws InterruptedException {
        //添加可中断的锁
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) //队列中没有数据时,需要阻塞,直到有数据put进入队列通知该操作可以继续执行
                notEmpty.await();
            //有数据时
            return extract();
        } finally {
            //释放锁
            lock.unlock();
        }
    }

    private E extract() {
        final Object[] items = this.items;
        E x = this.<E>cast(items[takeIndex]);
        items[takeIndex] = null;
        takeIndex = inc(takeIndex);
        --count;
        //发出通知 通知put方法,唤醒put操作
        notFull.signal();
        return x;
    }

ArrayBlockingQueue特点:

1.底层数据结构是数组,且数组大小一旦确定不可更改

2.不能存储null

3.阻塞功能是通过一个锁和两个隶属于该锁的Condition进行通信完成阻塞

LinkedBlockingQueue特点:无界阻塞队列

LinkedBlockingQueue由两个锁和分别隶属于两个锁的Condition以及用于计数的AtomicInteger

底层数据结构是链表,都是采用头尾节点,每个节点执行下一个节点的结构

数据存储在Node结构中

引入两把锁,一个入队列锁,一个出队列锁。满足同时有一个队列不满的Condition和一个队列不空的Condition

为什么使用两把锁,一把锁是否可以?

一把锁完全可以,一把锁意味着入队列和出队列同时只能有一个再进行,另一个必须等待释放锁,而从实际上来看,head和list是分离的,相互独立的,入队列是不会修改出队列的数据的,同理,出队列也不会修改入队列,这两个操作实际是相互独立的,这个锁相当于两个写入锁,入队列是一种写操作,操作head,出队列也是一种写操作,操作的是tail,这两个操作是无关的

SynchronousQueue:同步队列

SynchronousQueue:同步队列:每个插入操作必须等待另一个线程的移除操作,同样,任何一个线程的移除操作都要等待另一个线程的插入操作,因此此队列其实没有任何一个数据,或者说容量为0,

SynchronousQueue更像一个管道,不像容器,资源从一个方向快速的传递到另一个方向

队列对比:

 

如果不需要阻塞队列,优先选择ConcurrentLinkedQueue;

如果需要阻塞队列,队列大小固定优先选择ArrayBlockingQueue,

队列大小不固定优先选择LinkedBlockingQueue;

如果需要对队列进行排序,选择PriorityBlockingQueue;

如果需要一个快速交换的队列,选择SynchronousQueue;

如果需要对队列中的元素进行延时操作,则选择DelayQueue。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值