线程阻塞队列

阻塞队列

一、BlockingQueue 接口

  • BlockingQueue 是阻塞队列接口
  • 实现机制是使用两条线程,允许两个线程同时操作队列
  • 一个线程用于写入 Put ,一个线程用于读取 Take
  • 当队列中没有数据的情况下,读取线程会自动阻塞,直到有数据放入队列
  • 当队列中数据写满,写入线程被自动阻塞
  • 在保证并发的同时,提高了队列的存取效率

二、实现类

1、ArrayBlockingQueue (基于数组)

(1)实现原理

ArrayBlockingQueue 是一个有界队列,基于数组实现,在 ArrayBlockingQueue 内部,维护了一个定长数组,以便缓存队列中的数据对象,按照FIFO的方式排序;入队与出队的操作,使用同一个 ReentrantLock 来进行控制;

(2)源码展示
java 复制代码
public class ArrayBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    // ArrayBlockingQueue使用定长数组做为存储结构
    final Object[] items;
            
	/** Main lock guarding all access */
    final ReentrantLock lock;

    // 创建时传入数组容量(长度)
    public ArrayBlockingQueue(int capacity) {
        this(capacity, false);
    }
}
(3)创建自定义线程池
java 复制代码
// 使用ArrayBlockingQueue创建自定义线程池
ExecutorService executorService = 
     new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS,  
                    new ArrayBlockingQueue<Runnable(10),
                            Executors.defaultThreadFactory(),
                            	new ThreadPoolExecutor.AbortPolicy());
(4)工作机制
  1. 若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待工作队列中
  2. 若等待队列已满,即超过ArrayBlockingQueue有界队列的初始化容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量
  3. 若大于maximumPoolSize,则执行拒绝策略

2、LinkedBlockingQueue(基于链表)

(1)实现原理

LinkedBlockingQueue是一个无界队列,基于单向链表结构,可以选择进行设置容量。如果不设置容量的话,最大长度为 Integer.MAX_VALUE。入队与出队的操作,使用不同ReentrantLock来进行控制,所以LinkedBlockingQueue 吞吐量通常要高于 ArrayBlockingQuene。

(2)源码展示
java 复制代码
public class LinkedBlockingQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, java.io.Serializable {

    // 单向链表Node节点
	static class Node<E> {
        E item;

        Node<E> next;

        Node(E x) { item = x; }
    }
	/** Lock held by take, poll, etc */
    private final ReentrantLock takeLock = new ReentrantLock();
    
    /** Lock held by put, offer, etc */
    private final ReentrantLock putLock = new ReentrantLock();
            
    // 按照Integer.MAX_VALUE设置容量
    public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
}

FixedThreadPool、SingleThreadExecutor线程池使用LinkedBlockingQueue 队列;

  • 注意:

由于LinkedBlockingQueue是无界队列,线程池的任务队列可以无限制的添加新的任务,在这种情况下maximumPoolSize 参数是无效的,当线程池中的数量达到核心线程数时,线程数也不会增加,后续的任务会直接加到等待队列中

当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题。

3、DelayedWorkQueue(基于数组)

(1)实现原理

DelayedWorkQueue是基于堆结构的延迟队列,基于数组实现,初始容量为16,leader线程用于获取堆顶元素(队列头部元素)。该队列根据指定的延迟时间从小到大排序,如果延迟时间相同,则根据插入到队列的先后排序。

java 复制代码
static class DelayedWorkQueue extends AbstractQueue<Runnable>
        implements BlockingQueue<Runnable> {
	private static final int INITIAL_CAPACITY = 16;
    private RunnableScheduledFuture<?>[] queue = new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
    private Thread leader = null;
}
  • ScheduledThreadPool线程池使用了这个队列
java 复制代码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

//.....

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());

}

4、PriorityBlockingQueue

(1)实现原理

PriorityBlockingQueue 是一个基于优先级的无界队列(优先级的判断通过构造函数传入的Compator或元素实现Comparable接口来决定)。

**注意:**PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。

(2)案例

每个订单使用一个线程进行支付,支付时按照订单金额的优先级。

java 复制代码
// 订单类
public class PayOrder implements Runnable, Comparable<PayOrder> {

	private int orderNo; // 订单编号
	private BigDecimal payment; // 支付金额

	public PayOrder(int orderNo, BigDecimal payment) {
		this.orderNo = orderNo;
		this.payment = payment;
	}

	@Override
	public int compareTo(PayOrder o) {
		
		return this.payment.compareTo(o.payment);
		
	}

	@Override
	public void run() {
		System.out.printf("订单编号为%d,订单金额为:¥%.1f的订单已完成支付!【%s】\n",orderNo,payment,Thread.currentThread().getName());
	}

	public int getOrderNo() {
		return orderNo;
	}

	public void setOrderNo(int orderNo) {
		this.orderNo = orderNo;
	}

	public BigDecimal getPayment() {
		return payment;
	}

	public void setPayment(BigDecimal payment) {
		this.payment = payment;
	}
}
java 复制代码
// 创建10个线程模拟订单支付
public class Test01 {
	
	public static void main(String[] args) {
		
		ThreadPoolExecutor pool=new ThreadPoolExecutor(2, 20, 10, TimeUnit.SECONDS, 
															new PriorityBlockingQueue<Runnable>());
		
		pool.execute(new PayOrder(1, new BigDecimal("1943")));
		pool.execute(new PayOrder(2, new BigDecimal("2000")));
		pool.execute(new PayOrder(3, new BigDecimal("4000")));
		pool.execute(new PayOrder(4, new BigDecimal("4356")));
		pool.execute(new PayOrder(5, new BigDecimal("6543")));
		pool.execute(new PayOrder(6, new BigDecimal("7433")));
		pool.execute(new PayOrder(7, new BigDecimal("234")));
		pool.execute(new PayOrder(8, new BigDecimal("1567")));
		
		pool.shutdown();		
	}
java 复制代码
// 运行结果
订单编号为1,订单金额为:¥1943.0的订单已完成支付!【pool-1-thread-1】
订单编号为2,订单金额为:¥7894.0的订单已完成支付!【pool-1-thread-2】
订单编号为10,订单金额为:¥1100.0的订单已完成支付!【pool-1-thread-1】
订单编号为4,订单金额为:¥1353.0的订单已完成支付!【pool-1-thread-2】
订单编号为3,订单金额为:¥3253.0的订单已完成支付!【pool-1-thread-1】
订单编号为7,订单金额为:¥3574.0的订单已完成支付!【pool-1-thread-2】
订单编号为8,订单金额为:¥3673.0的订单已完成支付!【pool-1-thread-1】
订单编号为6,订单金额为:¥5430.0的订单已完成支付!【pool-1-thread-2】
订单编号为5,订单金额为:¥6344.0的订单已完成支付!【pool-1-thread-1】
订单编号为9,订单金额为:¥8653.0的订单已完成支付!【pool-1-thread-2】

可以看到除了前2个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为2个(corePoolSize核心线程数)。

通过运行的代码我们可以看出PriorityBlockingQueue它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

5、SynchronousQueue

(1)实现原理

SynchronousQueue是一个同步队列,它是一个不存储元素的阻塞队列(内部没有保存元素的数据结构容器),每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。

CachedThreadPool线程池使用这个队列

(2)案例
java 复制代码
public class Main {
	public static void main(String[] args) {

		// maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略(直接抛出异常)
		ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 10, TimeUnit.MILLISECONDS,
				new SynchronousQueue<Runnable>(), 
				new ThreadPoolExecutor.AbortPolicy());

		// 执行的线程任务大于maximumPoolSize,执行拒绝策略
		for (int i = 1; i <= 3; i++) {
			pool.execute(new Runnable() {
				@Override
				public void run() {
					System.out.println(Thread.currentThread().getName() + "被执行!");
				}
			});
		}
		
		// 关闭线程池
		pool.shutdown();
	}
}

说明:

  • 当任务队列为 SynchronousQueue,创建的线程数大于 maximumPoolSize 时,直接执行了拒绝策略抛出异常。
  • 使用 SynchronousQueue 队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于 maximumPoolSize ,则尝试创建新的线程,如果达到 maximumPoolSize 设置的最大值,则根据你设置的handler执行拒绝策略。
  • 因此在使用了 SynchronousQueue 队列的线程池中,你提交的线程任务不会被存入工作队列,而是会被马上执行,在这种情况下,你需要对程序的并发量有个准确的评估,才能设置合适的 maximumPoolSize数量,否则很容易就会执行拒绝策略。
相关推荐
MapGIS技术支持4 分钟前
MapGIS Objects Java计算一个三维点到平面的距离
java·开发语言·平面·制图·mapgis
中科固源6 分钟前
应用层|低空应用安全的 “精工锻造者”,中科数测以多工具矩阵赋能应用从开发到运维的全周期安全
安全·网络安全·低空
Coder_Boy_7 分钟前
业务导向型技术日志首日记录(业务中使用的技术栈)
java·驱动开发·微服务
码事漫谈23 分钟前
国产时序数据库崛起:金仓凭什么在复杂场景中碾压InfluxDB
后端
上进小菜猪29 分钟前
当时序数据不再“只是时间”:金仓数据库如何在复杂场景中拉开与 InfluxDB 的差距
后端
八月的雨季 最後的冰吻1 小时前
FFmepg-- 32-ffplay源码- PacketQueue 的线程安全机制 以及 serial 字段的作用
安全·ffmpeg
盖世英雄酱581361 小时前
springboot 项目 从jdk 8 升级到jdk21 会面临哪些问题
java·后端
济南壹软网络科技有限公司1 小时前
企业级盲盒系统:Java高并发架构在多元化抽奖电商中的设计与实践
java·架构·开源源码·盲盒源码·盲盒h5·盲盒app
廋到被风吹走1 小时前
【Java】常用设计模式及应用场景详解
java·开发语言·设计模式
一条可有可无的咸鱼2 小时前
企业招聘信息,企业资讯进行公示
java·vue.js·spring boot·uni-app