使用非阻塞队列的时候有一个很大问题就是:它不会对当前线程产生阻塞,那么在面对类似消费者-生产者的模型时,就必须额外地实现同步策略以及线程间唤醒策略,这个实现起来就非常麻烦。但是有了阻塞队列就不一样了,它会对当前线程产生阻塞,比如一个线程从一个空的阻塞队列中取元素,此时线程会被阻塞直到阻塞队列中有了元素。当队列中有元素后,被阻塞的线程会自动被唤醒(不需要我们编写代码去唤醒)。这样提供了极大的方便性。
具体说明:
java阻塞队列应用于生产者消费者模式、消息传递、并行任务执行和相关并发设计的大多数常见使用上下文。
BlockingQueue在Queue接口基础上提供了额外的两种类型的操作,分别是获取元素时等待队列变为非空和添加元素时等待空间变为可用。
BlockingQueue定义的常用方法如下:
抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time, unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用
插入操作是指向队列中添加一个元素,至于元素存放的位置与具体队列的实现有关。移除操作将会移除队列的头部元素,并将这个移除的元素作为返回值反馈给调用者。检查操作是指返回队列的头元素给调用者,队列不对这个头元素进行删除处理。
抛出异常形式的操作,在队列已满的情况下,调用add方法将会抛出IllegalStateException异常。如果调用remove方法时,队列已经为空,则抛出一个NoSuchElementException异常。(实际上,remove方法还可以附带一个参数,用来删除队列中的指定元素,如果这个元素不存在,也会抛出NoSuchElementException异常)。如果调用element检查头元素,队列为空时,将会抛出NoSuchElementException异常。
特殊值操作与抛出异常不同,在出错的时候,返回一个空指针,而不会抛出异常。
阻塞形式的操作,调用put方法时,如果队列已满,则调用线程阻塞等待其它线程从队列中取出元素。调用take方法时,如果阻塞队列已经为空,则调用线程阻塞等待其它线程向队列添加新元素。
LinkedBlockingQueue:
基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
阻塞队列:线程安全
按FIFO(先进先出)排序元素。队列的头部是在队列中时间最长的元素。队列的尾部是在队列中时间最短的元素。新元素插入到队列的尾部,并且队列检索操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,但是在大多数并发应用程序中,其可预知的性能要低。
注意:
1、必须要使用take()方法在获取的时候达成阻塞结果。
2、使用poll()方法将产生非阻塞效果。
然后看看两个关键方法的实现:put()和take():
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
从put方法的实现可以看出,它先获取了锁,并且获取的是可中断锁,然后判断当前元素个数是否等于数组的长度,如果相等,则调用notFull.await()进行等待。
当被其他线程唤醒时,通过enqueue (e)方法插入元素,最后解锁。
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}
它是一个private方法,插入成功后,通过notEmpty唤醒正在等待取元素的线程。
下面是take()方法的实现:
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
跟
put
方法实现很类似,只不过
put
方法等待的是
notFull
信号,而
take
方法等待的是
notEmpty
信号。在
take
方法中,如果可以取元素,则通过
dequeue
方法取得元素,下面是
dequeue
方法的实现:
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length)
takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
跟enqueue方法也很类似。
其实从这里应该明白了阻塞队列的实现原理,事实它和我们用Object.wait()、Object.notify()和非阻塞队列实现生产者-消费者的思路类似,
只不过它把这些工作一起集成到了阻塞队列中实现。
在项目中应用:
第一步:在项目启动时(调用init方法)创建一个单线程,用来处理任务的下发
package com.hikvision.energy.worker;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
import com.hikvision.energy.constant.InspectionConst;
import com.hikvision.energy.core.util.obj.ObjectUtils;
import com.hikvision.energy.util.CfgMgr;
import com.hikvision.energy.util.RESTTemplateUtil;
import com.hikvision.energy.vo.InspectionJobVO;
/**
* 巡检任务下发worker
*
* @author wanjiadong
* @date 创建时间:2017年11月22日 下午7:02:53
* @version 1.0
* @parameter
* @since
* @return
*/
public class InspectionJobWorker {
//日志
private static Logger log = LoggerFactory.getLogger(InspectionJobWorker.class);
//易变的
private static volatile boolean stop = false;
//连接时间
private static final int connetionTimeOut = 5000;
//传递数据时间
private static final int socketTimeOut = 10000;
//默认size为Integer.MAX_VALUE 2*31 -1,也可指定大小
//这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了
private static BlockingQueue<InspectionJobUnit> queue = new LinkedBlockingQueue<InspectionJobUnit>();
public void init(){
new InspectionJobThread().start();
}
static class InspectionJobThread extends Thread {
public void run(){
log.info("InspectionJobWorker start!");
InspectionJobUnit inspectionJobUnit = null;
OperationEnum methodType = null;
while(!stop){
try {
inspectionJobUnit = queue.take();
methodType = inspectionJobUnit.getMethodType();
//下发巡检任务url
String addUrl = "http://" +CfgMgr.getInspectionComponentsIp()+ ":" +CfgMgr.getInspectionComponentsPort()+ InspectionConst.INSPECTION_JOB_ADD_URL;
//删除url
String delUrl = "http://" +CfgMgr.getInspectionComponentsIp()+ ":" +CfgMgr.getInspectionComponentsPort()+ InspectionConst.INSPECTION_JOB_DELETE_URL;
switch(methodType){
case ADD:
InspectionJobVO inspectionJobVO = inspectionJobUnit.getInspectionJobVO();
//下发给巡检组件巡检任务
JSONObject resp = RESTTemplateUtil.doPOSTHttpByJSON(addUrl, JSONObject.toJSONString(inspectionJobVO), JSONObject.class, connetionTimeOut, socketTimeOut);
//resultCode为0代表下发成功
if(ObjectUtils.isObjectNull(resp) || ObjectUtils.isObjectNull(resp.get("resultCode"))
|| !resp.get("resultCode").equals(InspectionConst.CONSTANT_ZERO)){
log.error("巡检任务下发失败,任务id为:"+inspectionJobVO.getJobId());
}
break;
case MODIFY:
/**
* 先删除,再添加
*/
String delJsonString = "{\"jobId\": "+inspectionJobUnit.getJobId()+"}";
//下发给巡检组件巡检任务
JSONObject delResp = RESTTemplateUtil.doPOSTHttpByJSON(delUrl, delJsonString, JSONObject.class, connetionTimeOut, socketTimeOut);
//resultCode为0代表下发成功
if(ObjectUtils.isObjectNotNull(delResp) && ObjectUtils.isObjectNotNull(delResp.get("resultCode"))
&& delResp.get("resultCode").equals(InspectionConst.CONSTANT_ZERO)){
InspectionJobVO jobVO = inspectionJobUnit.getInspectionJobVO();
//下发给巡检组件巡检任务
JSONObject addResp = RESTTemplateUtil.doPOSTHttpByJSON(addUrl, JSONObject.toJSONString(jobVO), JSONObject.class, connetionTimeOut, socketTimeOut);
//resultCode为0代表下发成功
if(ObjectUtils.isObjectNull(addResp) || ObjectUtils.isObjectNull(addResp.get("resultCode"))
|| !addResp.get("resultCode").equals(InspectionConst.CONSTANT_ZERO)){
log.error("更新巡检任务失败。巡检任务下发失败,任务id为:"+jobVO.getJobId());
}
} else {
log.error("更新巡检任务失败。删除jobId:"+inspectionJobUnit.getJobId());
}
break;
case DELETE:
String delString = "";
if(ObjectUtils.isObjectNull(inspectionJobUnit.getJobItemId())){
delString = "{\"jobId\": "+inspectionJobUnit.getJobId()+"}";
} else {
delString = "{\"jobId\": "+inspectionJobUnit.getJobId()+",\"jobItemId\":"+inspectionJobUnit.getJobItemId()+"}";
}
//下发给巡检组件巡检任务
JSONObject delResponse = RESTTemplateUtil.doPOSTHttpByJSON(delUrl, delString, JSONObject.class, connetionTimeOut, socketTimeOut);
if(ObjectUtils.isObjectNotNull(delResponse) || ObjectUtils.isObjectNotNull(delResponse.get("resultCode"))
|| !delResponse.get("resultCode").equals(InspectionConst.CONSTANT_ZERO)){
log.error("删除巡检任务失败。删除jobId:"+inspectionJobUnit.getJobId());
}
break;
default:break;
}
} catch (InterruptedException e) {
log.error("InspectionJobThread error" + e.getMessage());
} catch (Exception ex){
log.error("InspectionJobThread doPOSTHttpByJSON error" + ex.getMessage());
}
}
}
}
/**
* 添加需要下发的巡检任务
*
* @author wanjiadong
* @date 创建时间:2017年11月22日 下午7:59:57
* @param inspectionJobVO
* @throws InterruptedException
*/
public static void buildAdd(final InspectionJobVO inspectionJobVO) throws InterruptedException{
InspectionJobUnit inspectionJobUnit = new InspectionJobUnit(inspectionJobVO, OperationEnum.ADD);
if(ObjectUtils.isObjectNotNull(inspectionJobUnit)){
queue.put(inspectionJobUnit);
}
}
/**
* 添加需要更新的巡检任务
*
* @author wanjiadong
* @date 创建时间:2017年11月22日 下午8:04:37
* @param inspectionJobVO
* @param jobId
* @throws InterruptedException
*/
public static void buildModify(final InspectionJobVO inspectionJobVO, final Long jobId) throws InterruptedException{
InspectionJobUnit inspectionJobUnit = new InspectionJobUnit(inspectionJobVO, OperationEnum.MODIFY, jobId);
if(ObjectUtils.isObjectNotNull(inspectionJobUnit)){
queue.put(inspectionJobUnit);
}
}
/**
* 添加需要删除的巡检任务id
* jobItemId不为空,则删除巡检
*
* @author wanjiadong
* @date 创建时间:2017年11月30日 下午6:46:12
* @param jobId
* @throws InterruptedException
*/
public static void buildDelete(final Long jobId, final Long jobItemId) throws InterruptedException{
InspectionJobUnit inspectionJobUnit = new InspectionJobUnit(null, OperationEnum.DELETE, jobId, jobItemId);
if(ObjectUtils.isObjectNotNull(inspectionJobUnit)){
queue.put(inspectionJobUnit);
}
}
/**
* 清空队列
*
* @author wanjiadong
* @date 创建时间:2017年11月23日 下午4:42:50
*/
public static void clearQueue(){
queue.clear();
}
}