Java线程池(三)之阻塞队列BlockingQueue详解

阻塞队列BlockingQueue(又叫工作队列,又叫任务队列)(在线程池的构造函数中的第五个参数,用于存放待处理的任务,待分配给线程)

 

里面装的不是线程哦,是元素,例如生产者-消费者中,阻塞队列装的是产品,这里的阻塞队列可以理解成仓库

线程是阻塞队列的操纵者,从里面取或往里面放。当使用put(e)、take()时,线程才会被阻塞,但也不是放进BlockingQueue中。

在线程池里面,工作队列(也即阻塞队列)里面装的是代办业务,而不是线程,ok?OK!

 

队列的接口结构:

List和BlockingQueue是同级的。

虽然:在BlockingQueue和Collection之间还有一个Queue。

严格来说,List、Set、Queue都属于Collection

单看Queue这一块:

 

队列的实现类:

BlockingQueue接口有七个实现类(重点讲红色三个)

其中:
    *ArrayBlockingQueue中的“有界”是指:容量大小有限,需要扩容。没有默认容量,需要在构造函数中显式指定。
    *LinkedBlockingQueue中虽然也有“有界”,但注意它的默认值大小为Integer.MAX_VALUE,接近21亿,太大了,可以看做无界      了。在自定义线程池的时候,阻塞队列一般使用LinkedBlockingQueue,只是需要指定容量为一个非              Integer.MAX_VALUE的值。

 

BlockingQueue接口有七个实现类(重点讲红色三个)

一、ArrayBlockingQueue

ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

ArrayBlockingQueue和ArrayList不一样,没有默认初始容量为10,又既然它有界,所以需要在构造函数中自己指明它的初始容量大小。(此处为3)

 


下面的针对所有BlockingQueue都成立。

 

重点是take()、poll(time, unit)

方法:( 重点是take()、poll(time, unit))

 

add(e):添加并返回Boolean,队满会直接抛出异常:Queue full。(所以add(e)永远不会返回false,因为直接报错了)

remove():移除并返回移除的值(队头元素),队空会直接抛出异常:NoSuchElementException

element():获得队头元素,为空时也会直接抛出异常。(NoSuchElementException,和remove()抛的异常一样)

 

不会抛错:

offer(e):添加成功返回true,队满返回false.

poll():移除并返回移除的值(队头元素),队空返回null

peek():获得队头元素,为空时返回null

 

put(e):添加元素,函数类型为void,队满则添加元素的线程阻塞

take():移除元素并返回移除的值(队首元素),队空则移除元素的线程阻塞(线程池中核心线程从阻塞队列(任务队列)中取任务的方式,就是通过take())(这里就解释了为什么核心线程不会在keepAlive时间后被终止)

 

offer(e,time,unit):添加成功返回true,队满返回false,并且添加元素的线程会被阻塞

poll(time,unit):移除并返回移除的值(队头元素),队空返回null,并且移除元素的线程会被阻塞,一段时间后自动释放(线程池中临时线程从阻塞队列(任务队列)中取任务的方式,就是通过poll(keepAliveTime,unit),时间一到就终止该临时线程,释放资源))

 

二、SynchronousQueue(CachedThreadPool默认使用它作为阻塞队列,这本身没有问题,问题出在CachedThreadPool自己的最大线程数为Integer.MAX_VALUE,可能导致线程数量过多,造成OOM)

没有容量,是一个不存储元素的BlockingQueue。

每一个put都要必须等待一个take,否则不能继续添加元素,反之亦然。

 

经过代码验证,SynchronousQueue是不存储元素的,连一个都不存!!

即:只要调用put(e)往里面放,该线程就会被阻塞,而不是想当然理解的“当队列为空,往里面放一个,OK,没有任何问题,再放一个时才会被阻塞”,这种想法是完全错误的!!!

当AAA线程执行了put(1)后,并不会马上打印出put 2。

 


BlockingQueue用在哪里:

生产者消费者模式、线程池(中的任务队列)、消息中间件

 

关于生产者消费者问题:

1.0版 synchronized wait notify(范式见并发编程基础p99-100)

2.0版 lock的conditon await signal

3.0版 BlockingQueue版

 

2.0版:

其实因为只有一个消费者线程、一个生产者线程,

即无需condition来区分不同类的线程(事实上,这里也只写了一个condition,所以这个代码很奇怪,,以后可以自己写一个版本)

(对于Condition更深的理解:一个Lock对象中可以创建多个Conditon对象,线程可以注册在指定的conditon中(在某线程中调用某个condition(如c1).await()即可完成所谓的“选择”,即让该线程在c1对象所对应的等待队列中等待被c1.signal()唤醒,从而进入创建了c1的lock对象(这个lock是唯一的,所以同步队列也是唯一的)的同步队列),而synchronized相当于整个Lock对象中只有一个单一的Conditon对象,所有线程都注册在它一个对象身上)

 

先lock,再看该不该自己(while,rather than if),不该自己就在while中c1.await()退出,放弃lock锁;

该自己就不会进入while,干完自己该干的后,c2.signal(),并在finally中lock.unlock()

 

 

资源类不继承Thread类或者不实现Runnable、Callable接口时,可以理解成该资源类里面可以定义多个run、call方法,一举两得,即一个资源类就可以代替多个角色(几个成员方法就对应几个run方法,也就对应几个角色)

例如本例有两个方法increment、decreament,就对应两个run方法,相当于一个生产者、一个消费者了就!

在主线程通过new Thread(()->{

    资源类实例.某个方法();

}

这种lamba的形式来调用就可以啦~

)

 

写多线程高并发问题代码的口诀:

上联:线程操作资源类;

下联:上锁-判断-干活-唤醒-放锁;

横批:小心无效唤醒。

 

其中,判断要用while(),而不要用if() !!! 即wait()、await()方法要放在while()循环中

当然,如果你只有一个生产者,一个消费者,那就无所谓,不会出问题。

 

3.0版
(while(FLAG)并不是用来控制该生产者还是消费者线程了,而只是用来控制这整个过程是否持续下去,如果想让整个过程一直持续下去,FLAG事实上是一直都不会改变的,恒为TRUE即可,因为“该谁了”、“要不要加锁”这些都由阻塞队列里的offer(e,时间,单位)、poll(时间,单位)一手包办了)
 
运行结果:
 
总结一下使用BlockingQueue的好处:
 
试着看书,再去回答这些问题:
在阻塞队列中拿数据、从阻塞队列中取数据的线程们一旦操作失败,会进入阻塞状态,即进入所谓的同步队列......吗??????    (感觉正确答案既不是等待队列,也不是同步队列呢...)
还是park()那种感觉的阻塞??????
或者说是进入了同步队列,自旋几次后被park()阻塞??????
 
回答:
问:在阻塞队列中拿数据、从阻塞队列中取数据的线程们一旦操作失败,会进入阻塞状态,即进入所谓的同步队列......吗??????
答:
对阻塞队列操作失败,线程会从同步队列的首节点(是同步队列的首节点,说明当前线程已经拿到锁了)移动到等待队里中。
 
线程操作阻塞队列失败时,线程会进入等待(WAITING)状态。
 
我为什么敢这么说,因为我去看了阻塞队列的put()方法的源码,里面就是先获取锁,然后在while里判断阻塞队列里的资源数量是否满了,如果满了就会调用condition1.await()。而await()里面调用了park()方法!!!此时被阻塞队列阻塞了的线程是 WAITING状态
 
这样就把一切都串起来了!!我再理理:调用await()方法,会使线程从同步队列的首节点移动到等待队列,调用await()方法会让线程放弃已经获得的锁,进入等待队列,并调用park()方法,使得等待队列中的线程只能通过获得及时通知(其他线程掉用unpark())或者过早通知(interrupt())才能进入同步队列。
 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值