线程阻塞队列

阻塞队列

一、BlockingQueue 接口

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

在这里插入图片描述

二、实现类

1、ArrayBlockingQueue (基于数组)
(1)实现原理

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

(2)源码展示
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)创建自定义线程池
// 使用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)源码展示
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线程用于获取堆顶元素(队列头部元素)。该队列根据指定的延迟时间从小到大排序,如果延迟时间相同,则根据插入到队列的先后排序。

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线程池使用了这个队列
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)案例

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

// 订单类
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;
	}
}
// 创建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();		
	}

// 运行结果
订单编号为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)案例
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数量,否则很容易就会执行拒绝策略。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kⅈꫛᧁ269

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值