线程池-workQueue

线程池参数的 workQueue 决定了缓存任务的排队策略,对于不同的业务场景,我们可以使用不同的排队策略。

我们只需要实现BlockingQueue 这个接口即可。

介绍一下常用的有三种workQueue。

1. SynchronousQueue

SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者(即丢给空闲的线程去执行),必须等队列中的添加元素被消费后才能继续添加新的元素,否则会走拒绝策略,所以使用SynchronousQueue阻塞队列一般要求线程池的maximumPoolSize为无界,避免线程拒绝执行操作。

插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列。

2. LinkedBlockingQueue

LinkedBlockingQueue如果不指定大小,默认值是 Integer.MAX_VALUE

源码在此:

public LinkedBlockingQueue() {
  this(Integer.MAX_VALUE);
}
@Native public static final int MAX_VALUE = 0x7fffffff;

就是说这个队列里面可以放 2^31-1 = 2147483647 个 任务,也就是无界队列。

所以为了避免队列过大造成机器负载或者内存爆满的情况出现,我们在使用的时候建议手动传一个队列的大小。

与ArrayBlockingQueue对比:

  1. 队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
  2. 数据存储容器不同,ArrayBlockingQueue采用的是数据作为数据存储容器,而LinkedBlockingQueue采用的则是以node节点为连接对象的链表。
  3. 由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据时,对于GC可能存在很大的影响。
  4. 两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加和移除操作采用的同一个ReentrantLock锁,而LinkedBlockingQueue使用的锁是分离的,添加采用的是putLock,移除采用的是takeLock,这样大大提高了队列的吞吐量,意味着高并发场景下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

与之类似的是 LinkedBlockingDeque。

LinkedBlockingDeque: 使用双向队列实现的双端阻塞队列,双端意味着可以像普通队列一样FIFO(先进先出),可以像栈一样FILO(先进后出)

3. DelayQueue

DelayQueue是一个延迟队列,无界,队列中每个元素都有过期时间,当从队列获取元素时,只有过期元素才会出队列,而队列头部的元素是过期最快的元素。

能够准确的把握任务的执行时间,通常可以使用在:

1、定时任务调度,比如订单过期未支付自动取消

2、缓存

 
posted on 2024-01-28 12:16  zhengbiyu  阅读(166)  评论(0编辑  收藏  举报