【重难点】【JUC 03】怎么实现一个线程安全的队列、手写模拟实现一个阻塞队列

【重难点】【JUC 03】怎么实现一个线程安全的队列、手写模拟实现一个阻塞队列

一、怎么实现一个线程安全的队列

Java 提供的线程安全的队列可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是 BlockingQueue,非阻塞队列的典型例子是 ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列

1.阻塞队列

顾名思义,可以提供阻塞功能的队列

阻塞队列提供的常用方法:

可能报异常可能阻塞返回布尔值设定等待时间
入队add(e)offer(e)put(e)offer(e, timeout, unit )
出队remove()poll()take()poll(timeout, unit)
查看element()peek()
  • add、remove、element 方法不会阻塞线程,当不满足约束条件时,会抛出 IllegalStateException 异常。例如:当队列满时调用 add
  • offer、poll、peek 方法既不会阻塞线程,也不会抛出异常。例:当队列满时调用 offer,则不会插入元素,会返回 false
  • 要想实现阻塞功能,就要调用 put 和 take 方法

ArrayBlockingQueue

基于数组的阻塞队列,在 ArrayBlocking 内部,维护了一个定长数组,以便缓存队列中的数据对象。此外,内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置

ArrayBlockingQueue 在生产者放入数据和消费者获取数据时,用的是同一个锁对象,这也意味着两者无法真正并行运行,这一点尤其不同于 LinkedBlockingQueue。按照实现原理来分析,ArrayBlockingQueue 完全可以采用分离锁,从而实现生产者和消费者完全并行运行。之所以不这么做是因为 ArrayBlockingQueue 的数据写入和获取已经足够轻巧,以至于引入分离锁,除了给代码带来不必要的复杂性外,在性能上没有任何优势。ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于:前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于 GC 还是有一定的影响

LinkedBlockingQueue

基于链表的阻塞队列,与 ArrayListBlockingQueue 类似,其内部也维护了一个数据缓冲队列,区别在于这个队列由链表构成。当生产者向队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回。只有当队列缓冲区达到最大值缓存容量时(LinkedBlcokingQueue 可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,对于消费者的处理也是如此。LinkedBlockingQueue 之所以能够高效地处理并发数据,还有一个原因,其对于生产者和消费者采用了分离锁来控制数据同步。需要注意的是,如果创建了一个 LinkedBlockingQueue 对象而没有指定容量,LinkedBlockingQueue 会默认设置为 Integer.MAX_VALUE。这样就会带来一个问题,如果生产者的生产速度远大于消费者的消费速度,可能会导致 OOM

2.非阻塞队列

当许多线程共享访问一个公共集合时,ConcurrentLinkedQueue 是一个恰当的选择,此队列不允许 null 元素

3.总结

在并发编程中,一般推荐使用阻塞队列,这样可以尽量避免程序出现意外的错误。阻塞队列最经典的应用场景就是 socket 客户端数据的读取和解析,读取数据的线程不断地将数据放入队列,然后解析线程不断地从队列获取数据解析

使用非阻塞队列虽然可以即使返回结果或者消费结果,但是必须自行编码解决返回为空的情况和消费重试等问题

它们都是线程安全的,不用考虑线程同步问题

二、手写模拟实现一个阻塞队列

1.使用 synchronized 实现

由于 synchronized 是同一把锁,所以使用 notify() 可能会唤醒非目标线程,notifyAll() 唤醒全部线程则会带来大量的 CPU 上下文交换和锁竞争

public class ArrayBlockingQueue{
	private Object[] array;		//数组
	private int head;			//头
	private int tail;			//尾
	private volatile int size;	//元素个数

	public ArrayBlockingQueue(int capacity){
		this.array = new Object[capacity];
	}
	
	//写入元素
	public synchronized void put(Object o) throws InterruptedException{
		//当队列满时,阻塞
		while(size == array.length){
			this.awit();
		}
		array[tail++] = o;
		if(tail ==array.length){
			tail = 0;
		}
		size++;
		//唤醒线程
		this.notifyAll();
	}

	//取出元素
	public synchronized Object get() throws InterruptedException{
		//当队列为空,阻塞
		while(size == 0){
			this.wait();
		}
		Object o = array[head++];
		if(head == array.length){
			head = 0;
		}
		size--;
		//唤醒线程
		this.notifyAll();
		return o;
	}
}

2.使用 ReentrantLock

可以使用 Condition 指定要唤醒的线程,所以效率高

public class ArrayBlockingQueue{
	private Object[] array;		//数组
	private int head;			//头
	private int tail;			//尾
	private volatile int size;	//元素个数
	private ReentrantLock lock = new ReentrantLock();	//锁
	private Condition notEnpty = lock.newCondition();	//非空
	private Condition notFull = lock.newCondition();	//非满

	public ArrayBlockingQueue(int capacity){
		this.array = new Object[capacity];
	}

	//写入元素
	public void put(Object o) throws InterruptedException{
		try{
			lock.lock();
			//当队列满时,阻塞
			while(size == array.length){
				notFull.wait();
			}
			array[tail++] = o;
			if(tail == array.length){
				tail = 0;
			}
			size++;
			//唤醒线程
			notEmpty.notifyAll();
		}finally{
			lock.unlock();
		}
	}
	
	//取出元素
	public Object get() throws InterruptedException{
		 lock.lock();
		 try{
		 	//当队列为空,阻塞
		 	while(size == 0){
		 		notEmpty.wait();
		 	}
		 	Object o = array[head++];
		 	if(head == array.length){
		 		head = 0;
		 	}
		 	size--;
		 	//唤醒线程
		 	notFull.notyfyAll();
		 	return o;
		 }finally{
		 	lock.unlock();
		 }
	}
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

313YPHU3

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

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

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

打赏作者

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

抵扣说明:

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

余额充值