线程池-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对比:
- 队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
- 数据存储容器不同,ArrayBlockingQueue采用的是数据作为数据存储容器,而LinkedBlockingQueue采用的则是以node节点为连接对象的链表。
- 由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据时,对于GC可能存在很大的影响。
- 两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加和移除操作采用的同一个ReentrantLock锁,而LinkedBlockingQueue使用的锁是分离的,添加采用的是putLock,移除采用的是takeLock,这样大大提高了队列的吞吐量,意味着高并发场景下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
与之类似的是 LinkedBlockingDeque。
LinkedBlockingDeque: 使用双向队列实现的双端阻塞队列,双端意味着可以像普通队列一样FIFO(先进先出),可以像栈一样FILO(先进后出)
3. DelayQueue
DelayQueue是一个延迟队列,无界,队列中每个元素都有过期时间,当从队列获取元素时,只有过期元素才会出队列,而队列头部的元素是过期最快的元素。
能够准确的把握任务的执行时间,通常可以使用在:
1、定时任务调度,比如订单过期未支付自动取消
2、缓存