秋招准备-Java-并发编程-阻塞队列(五)

1.Queue框架

2.生产者消费者模式与阻塞队列

3.阻塞队列实现类


1.Queue框架

    Java的集合类提供了Queue队列的实现,可以分为四个接口来对应相应的队列功能:

    Queue:普通队列,头出尾进,一般用于满足FIFO先进先出功能。

    Deque:双端队列,双端进出,如果需要实现LIFO后进先出的堆栈功能,优先考虑Deque的实现类,而非Stack接口。

    BlockingQueue:阻塞队列,提供带阻塞功能的存入取出操作,头出尾进,用于实现生产者消费者模式。

    BlockingDeque:双端阻塞队列,用于更复杂的生产者消费者模型。


    1.Queue接口

        队列的顶级接口,提供了队列的基本操作方法。

        两套插入取出操作:

        add/remove/element

boolean add(E e)E remove()E element()
从队尾添加一个元素从队头取出一个元素返回队头元素,但不在队列中删除

        offer/poll/peek

boolean offer(E e)E poll()E peek()
从队尾添加一个元素从队头取出一个元素返回对头元素,但不在队列中删除

        队列提供Collection的插入、提取、检查操作,区别是,add()与remove()操作失败时,会抛出异常,即不允许在队列满时插入在队列空时取出。而offer()在满队列插入时返回false,poll()在空队列取出时返回null。

        如果使用链表系的实现,则队列无界,不会出现不允许插入的情况,如果使用数组系的实现,则看具体会不会自动扩容。

        如果要使用队列,则考虑LinkedList(无界)与PriorityQueue(自动扩容)。


    2.Deque接口

        双端队列即在Queue基础上改为了首尾都能插入取出,同样是两套方法,

addFirst(E e)E removeFirst()peekFirst()
从队头插入从队头取出检查

        用双端队列来实现堆栈:

        Deque<E> stack = new ArrayDeque<E>();

        主要实现即Deque的ArrayDeque,和BlockingDeque的LinkedBlockingDeque。


    3.BlockingQueue接口

        阻塞队列提供了两个附加操作,即在队列满时阻塞添加,和队列空时阻塞取出。

        所以在队列满的时候,添加操作会暂停直到能够添加的时候才执行,队列空的取出操作也是一样。

 阻塞超时
插入put(E e)offer(e,time,unit)
取出E take()E poll(time,unit)



2.生产者-消费者模式

    在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。

    单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。

    特点:

    1.解耦合

    假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。

    2.支持并发

    生产者如果已经生产出来产品(数据)等待消费者使用,如果是直接耦合,需要消费者来接收的话,则生产者会拿着产品一直进入等待状态(阻塞),直到消费者拿走,那么如果消费者自身陷入了一段长时间的操作,生产者这边一直阻塞就会有很大的性能损耗。

    而如果有缓冲区的话,则生产者只需要将产品往缓冲区放,就可以继续去生产下一个产品,这种情况主要是在生产者和消费者的处理速度不同的情况下,能够有很大的改善。

    说明:

    现有生产者1,生产产品1,消费者1,消费产品1.

    1.生产者1与消费者1直接耦合时

    生产者1生产了一个产品1,而此时消费者1暂时不需要,则生产者1一直等待。

    突然,消费者1需要大量的产品时,则拿走一开始生产的产品1,生产者开始继续生产,但消费者很快就用完了产品1,则只能等待生产者一个一个的生产。

    2.生产者1与消费者1用普通队列连接时

    生产者1生产出产品,不需要等待消费者,直接放进队列里,继续生产,继续放进。

    消费者需要产品时,直接在队列里拿,如果消费者使用产品的速度很快,只要队列中还有产品,可以一直拿,不需要等待生产者的即时生产。

    问题:当生产者不断生产而消费者不拿时,缓冲的队列会满,这时,涉及到实现问题,可选的就两种,要么放弃要么阻塞,但放弃这个产品就为了继续生产,显然有些无用,所以可以选择让生产者进入类似直接耦合一样的阻塞状态,只是等待的时缓冲队列不满,这也就是阻塞队列的实现。

    而当消费者在取产品遇到了空队列时也是如此,不可能取消本次操作,则同样陷入阻塞,等待队列不为空时再取产品。



3.阻塞队列实现

    1.ArrayBlockingQueue

    使用数组实现的阻塞队列。采取FIFO,新元素插入到队尾,获取则是从头部获取元素。

    属于有界缓冲队列,队列的大小固定,初始化之后就不能更改了,因为大小固定,所以生产者和消费者都会有缓冲情况,即队列为空时获取会阻塞,队列已满时插入会阻塞。

构造器:
	ArrayBlockingQueue<E>(int capacity);
常用方法:
	put(E e);//阻塞放入元素
	E take();//阻塞取出元素
实际上只有put()和take()方法是阻塞实现的插入和取出,(底层通过Lock的Condition来实现)
在阻塞队列中用add()和remove()也是可以的,在为空为满时报错而不是阻塞
不过在生产者消费者模式中一般都用阻塞方法
	


    2.LinkedBlockingQueue

    使用链表实现的阻塞队列。

    使用上和ArrayBlockingQueue基本一样,采用FIFO,插入队尾,取出队头。

    链表实际无容量限制,但有定义一个capacity,用来限制大小,在无参构造中,capacity默认int最大值,而在有参时可以指定,当指定capacity后,达到capacity时插入元素会阻塞。

构造器:
	LinkedBlockingQueue<E>();
	LinkedBlockingQueue<E>(int capacity);
常用方法:
	put(E e);//阻塞放入元素
	E take();//阻塞取出元素


    3.SynchronousQueue

    无缓冲区的阻塞队列,每个插入操作必须等待另一个线程的对应取出操作,反之取出也要等插入。(等于是一开始的耦合实现,只是还是添加的中间的中介,让生产者和消费者的代码没有直接耦合在一起。)

    在Executors.newCachedThreadPool()构造的线程池中,阻塞队列参数即为SynchronousQueue(),那么多个线程就相当于多个生产者,线程池的sumbit,相当于生产者提交线程,然后经过阻塞队列,不进行存储,直接提交到消费者处,进行中线程处,开始运行。

构造器:
	SynchronousQueue<E>();
	SynchronousQueue<E>(int capacity);
常用方法:
	put(E e);//阻塞放入元素
	E take();//阻塞取出元素

    4.DelayQueue<E extends Delayed>

    无界阻塞队列,用作延迟队列,可以添加带有延迟时间的任务。

    内部通过优先级队列PriorityQueue实现,并且添加的元素必须实现Delayed接口。

    Delayed接口说明:

	public interface Delayed
	{
		long getDelay(TimeUnit unit);
	}
	class DelayTask implements Delayed
	{
		private long delay;    //延迟时间
		private long expire;   //预计到达时间
		
		DelayTask(long delay)
		{
			this.delay = delay;
			expire = System.currentTimeMillis() + delay;
		}
		//距离预计时间还剩多少时间
		long getDelay(TimeUnit unit)
		{
			return (long)(expire - System.currentTimeMillis());
		}
		public int compareTo(Delayed o) 
		{
			return (int) (this.getDelay(TimeUnit.MILLISECONDS) -o.getDelay(TimeUnit.MILLISECONDS));
		}
	}

    重写compareTo()为了在优先级队列中实现排序。

    队列添加实现了Delayed接口的元素,然后put()与take()会按照元素本身设定的delay延时,进行相应操作。

    

    5.PriorityBlockingQueue

    ……

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值