(一)BlockingQueue的原理
1. 什麼是BlockingQueue?
阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:在隊列爲空時,獲取元素的線程會等待隊列變爲非空。當隊列滿時,存儲元素的線程會等待隊列可用。阻塞隊列常用於生產者和消費者的場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器裏拿元素。
2. BlockingQueue的核心方法:
2.1放入數據:
offer(anObject):表示如果可能的話,將anObject加到BlockingQueue裏,即如果BlockingQueue可以容納,
則返回true,否則返回false.(本方法不阻塞當前執行方法的線程)
offer(E o, long timeout, TimeUnit unit),可以設定等待的時間,如果在指定的時間內,還不能往隊列中
加入BlockingQueue,則返回失敗。
put(anObject):把anObject加到BlockingQueue裏,如果BlockQueue沒有空間,則調用此方法的線程被阻斷
直到BlockingQueue裏面有空間再繼續.
2.2 獲取數據:
poll(time):取走BlockingQueue裏排在首位的對象,若不能立即取出,則可以等time參數規定的時間,
取不到時返回null;
poll(long timeout, TimeUnit unit):從BlockingQueue取出一個隊首的對象,如果在指定時間內,
隊列一旦有數據可取,則立即返回隊列中的數據。否則知道時間超時還沒有數據可取,返回失敗。
take():取走BlockingQueue裏排在首位的對象,若BlockingQueue爲空,阻斷進入等待狀態直到
BlockingQueue有新的數據被加入;
drainTo():一次性從BlockingQueue獲取所有可用的數據對象(還可以指定獲取數據的個數),
通過該方法,可以提升獲取數據效率;不需要多次分批加鎖或釋放鎖。
3. BlockingQueue的源碼
put方法的源碼
/**
* Inserts the specified element at the tail of this queue, waiting
* for space to become available if the queue is full.
*
* @throws InterruptedException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
final E[] items = this.items;
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
try {
while (count == items.length)
notFull.await();
} catch (InterruptedException ie) {
notFull.signal(); // propagate to non-interrupted thread
throw ie;
}
insert(e);
} finally {
lock.unlock();
}
}
BlockingQueue使用的是ReentrantLock:
/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/
/** Main lock guarding all access */
private final ReentrantLock lock;
/** Condition for waiting takes */
private final Condition notEmpty;
/** Condition for waiting puts */
private final Condition notFull;
從上面的源碼可以看出,當隊列滿的時候,會調用await()方法:
/**
* Implements interruptible condition wait.
* <ol>
* <li> If current thread is interrupted, throw InterruptedException
* <li> Save lock state returned by {@link #getState}
* <li> Invoke {@link #release} with
* saved state as argument, throwing
* IllegalMonitorStateException if it fails.
* <li> Block until signalled or interrupted
* <li> Reacquire by invoking specialized version of
* {@link #acquire} with saved state as argument.
* <li> If interrupted while blocked in step 4, throw exception
* </ol>
*/
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
long savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
BlockingQueue使用LockSupport.park(this)來停止線程, LockSupport是JDK中比較底層的類,用來創建鎖和其他同步工具類的基本線程阻塞原語。java鎖和同步器框架的核心 AQS: AbstractQueuedSynchronizer,就是通過調用 LockSupport .park()和 LockSupport .unpark()實現線程的阻塞和喚醒 的。 LockSupport 很類似於二元信號量(只有1個許可證可供使用),如果這個許可還沒有被佔用,當前線程獲取許可並繼 續 執行;如果許可已經被佔用,當前線 程阻塞,等待獲取許可。
關於該方法的具體實現,可以參考:
(http://blog.csdn.net/hengyunabc/article/details/28126139)
4.BlockingQueue在項目中應用
項目中,需要將錯誤日誌入庫,用戶在訪問時會首先經過特殊字符攔截器,如果出現訪問錯誤,會產生錯誤日誌,並會調用Log方法,將該錯誤日誌加入阻塞隊列:
/**
* 保存日誌信息到內存文件
*
* @param logEntity
* 日誌文件實體
*/
@Override
public void log(LogManager logEntity) {
synchronized (blockingQueue) {
if (logEntity != null) {
//如果報錯信息隊列大於隊列最大值,則證明保存隊列數據線程出現問題,則不繼續插入隊列
if(blockingQueue.size() < maxSize){
logEntity.setRequestmsg(StringTools.subLogMsg(logEntity.getRequestmsg(),4000));
logEntity.setResponsemsg(StringTools.subLogMsg(logEntity.getResponsemsg(),4000));
logEntity.setErrormsg(StringTools.subLogMsg(logEntity.getErrormsg(),4000));
blockingQueue.put(logEntity);
}
}
}
}
@Override
public void log(String requestmsg, String responsemsg, String errormsg) {
if(StringUtils.isEmpty(errormsg)){
return;
}
LogManager logEntity = new LogManager();
logEntity.setRequestmsg(StringTools.subLogMsg(requestmsg,4000));
logEntity.setResponsemsg(StringTools.subLogMsg(responsemsg,4000));
logEntity.setErrormsg(StringTools.subLogMsg(errormsg,4000));
logEntity.setCreattime(new Date());
try {
logEntity.setIp(CpicmIPUtil.getServerIp());
} catch (Exception e) {
}
synchronized (blockingQueue) {
//如果報錯信息隊列大於隊列最大值,則證明保存隊列數據線程出現問題,則不繼續插入隊列
if(blockingQueue.size() < maxSize){
blockingQueue.put(logEntity);
}
}
}
在做項目時,日誌要入庫,在spring容器初始化時啓動線程
spring的配置如下:
<!-- 啓動時加載錯誤日誌入庫線程 -->
<bean id="logSyncToDBThread" lazy-init="false"
class="com.smu.edward.threads.LogSyncToDBThread" init-method="startSync">
</bean>
LogSyncToDBThread類的定義如下:
public class LogSyncToDBThread extends Thread {
Logger logger = LoggerFactory.getLogger(LogSyncToDBThread.class);
@Autowired
private LogManagerService logManagerService;
private boolean isExited = true;
/**
* Run方法,線程10秒鐘運行一次保存日誌文件到數據庫
*/
public void run() {
isExited = false;
while (!isExited) {
if (LogManagerServiceImpl.getBlockingQueue() == null || LogManagerServiceImpl.getBlockingQueueSize() < 0) {
try {
Thread.sleep(10 * 1000);
} catch (Exception e) {
continue;
}
continue;
}
int count = 0;
try {
count = logManagerService.saveLogCacheToDB();
// if(count != 0){
// logger.info("向數據庫同步了" + count + "條審計日誌數據!");
// }
} catch (Exception e) {
logger.error(e.getMessage());
continue;
}
try {
Thread.sleep(10 * 1000);
} catch (Exception e) {
continue;
}
}
isExited = true;
}
/**
* 線程同步啓動方法
*/
public void startSync() {
start();
logger.info("錯誤日誌入庫線程啓動成功");
}
}
saveLogCacheToDB()方法定義如下:
@Service(value="logManagerService")
public class LogManagerServiceImpl implements LogManagerService {
//private static List<LogEntity> logCache = new ArrayList<LogEntity>();
private static final Logger logger = LoggerFactory.getLogger(LogManagerServiceImpl.class);
private static BlockingQueue blockingQueue = new BlockingQueue();
//最大隊列大小限制爲10000個,超過10000則不寫入隊列
private static final int maxSize = 10000;
@Autowired
private LogManagerRepo logManagerRepo;
/**
* 保存日誌信息到內存文件
*
* @param logEntity
* 日誌文件實體
*/
@Override
public void log(LogManager logEntity) {
synchronized (blockingQueue) {
if (logEntity != null) {
//如果報錯信息隊列大於隊列最大值,則證明保存隊列數據線程出現問題,則不繼續插入隊列
if(blockingQueue.size() < maxSize){
logEntity.setRequestmsg(StringTools.subLogMsg(logEntity.getRequestmsg(),4000));
logEntity.setResponsemsg(StringTools.subLogMsg(logEntity.getResponsemsg(),4000));
logEntity.setErrormsg(StringTools.subLogMsg(logEntity.getErrormsg(),4000));
blockingQueue.put(logEntity);
}
}
}
}
@Override
public void log(String requestmsg, String responsemsg, String errormsg) {
if(StringUtils.isEmpty(errormsg)){
return;
}
LogManager logEntity = new LogManager();
logEntity.setRequestmsg(StringTools.subLogMsg(requestmsg,4000));
logEntity.setResponsemsg(StringTools.subLogMsg(responsemsg,4000));
logEntity.setErrormsg(StringTools.subLogMsg(errormsg,4000));
logEntity.setCreattime(new Date());
try {
logEntity.setIp(CpicmIPUtil.getServerIp());
} catch (Exception e) {
}
synchronized (blockingQueue) {
//如果報錯信息隊列大於隊列最大值,則證明保存隊列數據線程出現問題,則不繼續插入隊列
if(blockingQueue.size() < maxSize){
blockingQueue.put(logEntity);
}
}
}
// 同步內存中的日誌信息到數據庫
public int saveLogCacheToDB() {
int result = 0;
try {
synchronized (blockingQueue) {
List<LogManager> logEntities = new ArrayList<LogManager>();
if (blockingQueue != null && !blockingQueue.isEmpty()) {
if (blockingQueue.size() < 200) {
// 保存到數據庫
try {
while(!blockingQueue.isEmpty()){
logEntities.add((LogManager)blockingQueue.take());
}
logManagerRepo.batchInsertLog(logEntities);
// 插入條數
result = logEntities.size();
} catch (Exception e) {
logger.error(e.getMessage());
return 0;
}
} else{
// 最多取出200條保存到數據庫
for (int i = 0; i < 200; i++) {
logEntities.add((LogManager)blockingQueue.take());
}
try {
// 保存到數據庫
logManagerRepo.batchInsertLog(logEntities);
// 插入條數
result = logEntities.size();
} catch (Exception e) {
logger.error(e.getMessage());
// 保存數據庫時發生異常,需要還原
for(LogManager log : logEntities){
blockingQueue.put(log);
}
return 0;
}
}
}
}
} catch (Exception e) {
logger.error(e.getMessage());
return 0;
}
return result;
}
public static BlockingQueue getBlockingQueue() {
synchronized (blockingQueue) {
return blockingQueue;
}
}
public static int getBlockingQueueSize() {
synchronized (blockingQueue) {
return blockingQueue.size();
}
}
public static void setBlockingQueue(BlockingQueue blockingQueue) {
LogManagerServiceImpl.blockingQueue = blockingQueue;
}
public Page<Map<String, Object>> searchLogManagerList(Pageable page,Map paramMap){
return this.logManagerRepo.searchLogManagerList(page, paramMap);
}
}
其中log方法:藉助Spring的攔截器調用log方法——如果在請求過程中,發生請求異常,則調用該方法,新建logManger實體類,並將其寫入BlockingQueue阻塞隊列!
其中LogManager實體類定義如下:
@Entity
@Table(name = "LOG_MANAGER")
public class LogManager implements java.io.Serializable {
/**
* 列名常量
*/
private static final String COL_ID = "id";
private static final String COL_REQUESTMSG = "requestmsg";
private static final String COL_RESPONSEMSG = "responsemsg";
private static final String COL_ERRORMSG = "errormsg";
private static final String COL_CREATETIME = "creattime";
private static final String COL_IP = "ip";
/**
* 列屬性
*/
@Id
@SequenceGenerator(name="SEQ_LOG_MANAGER", sequenceName="SEQ_LOG_MANAGER", allocationSize = 1)
@GeneratedValue(generator="SEQ_LOG_MANAGER",strategy=GenerationType.SEQUENCE)
@Column(name=COL_ID,unique = true, nullable = false, precision = 18, scale = 0)
private BigDecimal id;
@Column(name=COL_REQUESTMSG)
private String requestmsg;
@Column(name=COL_RESPONSEMSG)
private String responsemsg;
@Column(name=COL_ERRORMSG)
private String errormsg;
@Column(name=COL_CREATETIME)
private Date creattime;
@Column(name=COL_IP)
private String ip;
public BigDecimal getId() {
return id;
}
public void setId(BigDecimal id) {
this.id = id;
}
public String getRequestmsg() {
return requestmsg;
}
public void setRequestmsg(String requestmsg) {
this.requestmsg = requestmsg;
}
public String getResponsemsg() {
return responsemsg;
}
public void setResponsemsg(String responsemsg) {
this.responsemsg = responsemsg;
}
public String getErrormsg() {
return errormsg;
}
public void setErrormsg(String errormsg) {
this.errormsg = errormsg;
}
public Date getCreattime() {
return creattime;
}
public void setCreattime(Date creattime) {
this.creattime = creattime;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
}