阻塞队列 简单了解

BlockingQueue(阻塞队列):
在生产者和消费者模型中,通过队列可以实现两者数据共享,若在某个时间段内,生产出的数据的速度大于消费速度时,就会发生阻塞,为处理这种情况,这时就需要阻塞队列。
在多线程中,阻塞就是在某种情况下挂起线程,满足条件后,就要唤醒。

BlockingQueue基本方法:
1.放入数据
(1)offer(anObject):表示如果可以容纳的话,将元素添加进队列中,返回true,否则返回false.      
(2)offer(E o, long timeout, TimeUnit unit):在指定的时间内,如果元素可以添加进队列,返回true,否则返回false。
(3)put(anObject):把元素加到队列里,如果队列没有空间,则调用此方法的线程被阻断,直到里面有空间再继续.
 2. 获取数据
(1)poll(time):取走队列里第一个元素,若不能立即取出,则可以等time参数规定的时间,取不到时返回null;
(2)poll(long timeout, TimeUnit unit):从队列取出一个队首的元素,在指定时间内,数据可取,则立即返回队列中的数据。否则false。
(3)take():取走队列里排在首位的对象,若队列为空,阻断进入等待状态直到队列里面有新的数据被加入。

常见BlockingQueue:
1、ArrayBlockQueue:
基于数组实现的阻塞队列,在ArrayBlockQueue中,会定义一个定长数组来存储队列中的数据,该数组头尾相连,当缓存数据进入尾部后,下一节点为头部。

ArrayBlockingQueue可以使用两把锁提高效率吗?
不能,主要原因是ArrayBlockingQueue底层循环数组来存储数据,LinkedBlockingQueue底层链表来存储数据,链表队列的添加和删除,只是和某一个节点有关,为了防止head和last相互影响, 就需要有一个原子性的计数器,每个添加操作先加入队列,计数器+1,这样是为了保证队列在移除的时候,长度是大于等于计数器的,通过原子性的计数器,使得当前添加和移除互不干扰。对于循环数据来说,当我们走到最后一个位置需要返回到第一个位置,这样的操作是无法原子化,只能使用同一把锁来解决

ArrayBlockQueue put()源码

public void put(E e) throws InterruptedException {
    checkNotNull(e);//判断元素是否为null
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();//队列已满,等待
        enqueue(e);//元素入队
    } finally {
        lock.unlock();//释放锁
    }
}

private static void checkNotNull(Object v) {
    if (v == null)//元素为空,抛出异常
        throw new NullPointerException();
}

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();//唤醒线程
}

ArrayBlockQueue take()源码

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();//队列空则等待
        return dequeue();//取出首位元素
    } finally {
        lock.unlock();
    }
}

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;//将带取出元素值变为null,方便回收
    if (++takeIndex == items.length)
        takeIndex = 0;//如果取出的元素为尾部元素,下一次进入从头部开始取
    count--;
    if (itrs != null)
        itrs.elementDequeued();
    notFull.signal();
    return x;
}

2、LinkedBlockingQueue
基于链表的阻塞队列,如果没有指定容量大小,LinkedBlockingQueue会默认大小为Integer.MAX_VALUE。

当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时,才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。

LinkedBlockingQueue有两把锁独立进行控制,分别对于生产者端和消费者端,这样提高了并发效率。

ArrayBlockingQueue和LinkedBlockingQueue的区别和联系?
1)数据存储容器不一样,ArrayBlockingQueue采用数组去存储数据、LinkedBlockingQueue采用链表去存储数据
2)ArrayBlockingQueue(循环数组)采用数组去存储数据,不会产生额外的对象实例;
LinkedBlockingQueue采用链表去存储数据,在插入和删除元素只与一个节点有关,需要去生成一个额外的Node对象,这可能长时间内需要并发处理大批量的数据,对于性能和后期 GC会产生影响。
3)ArrayBlockingQueue是有界的,初始化时必须要指定容量;LinkedBlockingQueue默认是无界的Integer.MAX_VALUE, 当添加速度大于删除速度、有可能造成内存溢出。
4) ArrayBlockingQueue在读和写使用的锁是一样的,即添加操作和删除操作使用的是同一个 ReentrantLock,没有实现锁分离;LinkedBlockingQueue实现了锁分离,添加的时候采用putLock、删除的时候采用takeLock,这样能提高队列的吞吐量。

3、PrioityBlockingQueue
基于优先级的阻塞队列,优先级的判断通过构造函数传入的Compator对象来决定
PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。使用的时候要注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。
在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。

4、DelayQueue
DelayQueue基于PriorityBlockingQueue延迟阻塞队列,DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
DelayQueue是一个无界队列,因此往队列中插入数据的操作永远不会被阻塞,而只有获取数据的操作才会被阻塞。
DelayQueue主要用于缓存系统的设计、定时任务系统的设计

5、SynchrousQueue
SynchrousQueue是一个不存储元素的队列,每次put之后必须进行take操作,天然的实现了生产者消费者模型。
SynchrousQueue因为相比于其他阻塞队列没有缓冲区,数据吞吐量会降低,但因为没有缓冲区,相应的响应性能得到提高

声明一个SynchronousQueue有两种不同的方式,公平模式和非公平模式:

公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体现整体的公平策略;
非公平模式,SynchronousQueue默认:SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,在非公平锁中,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值