All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sviolet.thistle.model.queue.PurposefulBlockingMessagePool Maven / Gradle / Ivy

There is a newer version: 22.1.0
Show newest version
/*
 * Copyright (C) 2015-2017 S.Violet
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Project GitHub: https://github.com/shepherdviolet/thistle
 * Email: [email protected]
 */

package sviolet.thistle.model.queue;

import sviolet.thistle.util.concurrent.ThreadPoolExecutorUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 

目的性阻塞消息池

* *

*******************************************************************************************

* *

意外消息:若一个消息在塞入(restock)时, ID未在消息池注册, 则该消息被视为意外消息

* *

*******************************************************************************************

* *

模式1:意外消息直接抛弃模式

* *

用于提前知道ID的情况, 先注册ID, 然后阻塞等待异步操作塞入的消息.

* *

使用{@link PurposefulBlockingMessagePool#register(Object)}方法注册指定ID, 开始异步操作, 使用 * {@link PurposefulBlockingMessagePool#wait(Object, long)} 方法等待目标对象返回, 此时线程阻塞. 当 * 异步操作中, 通过{@link PurposefulBlockingMessagePool#restock(Object, Object)} 方法塞入目标对象后, * 原线程继续执行, 返回目标对象.

* *

{@code
 *
 *  PurposefulBlockingMessagePool pool = new PurposefulBlockingMessagePool<>();//意外消息直接抛弃模式
 *
 *  //注册需要的消息ID
 *  try {
 *      pool.register(id);
 *  } catch (PurposefulBlockingMessagePool.OutOfLimitException e) {
 *      //注册等待的消息超过限制
 *      //TODO异常处理,终止流程或抛出异常
 *      return;
 *  }
 *  //异步操作
 *  threadPool.execute(new Runnable(){
 *      public void run(){
 *          //TODO异步处理
 *          //将消息塞入消息池
 *          try {
 *              pool.restock(id, item);
 *          } catch (PurposefulBlockingMessagePool.MessageDropException e) {
 *              //因ID未注册或意外消息池满而抛出该异常
 *              //TODO处理被丢弃的消息
 *          }
 *      }
 *  });
 *  //阻塞线程, 等待消息返回
 *  String message = null;
 *  try {
 *      message = pool.wait(id, 5000);
 *  } catch (PurposefulBlockingMessagePool.TimeoutException e) {
 *      //等待超时抛出该异常
 *      //TODO超时处理,终止流程或抛出异常
 *      return;
 *  }
 *  if (message == null){
 *      //TODO返回消息为空,终止流程或抛出异常
 *      return;
 *  }
 *
 * }

* *

*******************************************************************************************

* *

模式2:意外消息池模式

* *

用于无法提前知道ID的情况, 设置一个意外消息有效期, 接收到的意外消息将存入意外消息池, 有效期内可以 * 从意外消息池获取该消息, 过期的消息将被清理任务清理(自动). 手动调用{@link PurposefulBlockingMessagePool#flush()} * 可立即启动清理任务.

* *
{@code
 *
 * PurposefulBlockingMessagePool pool = new PurposefulBlockingMessagePool<>(10000);//意外消息池模式, 设定意外消息有效期
 *
 * //异步操作,可能会先于pool.register()执行
 * threadPool.execute(new Runnable(){
 *      public void run(){
 *          //TODO异步处理
 *          //将消息塞入消息池
 *          try {
 *              pool.restock(id, item);
 *          } catch (PurposefulBlockingMessagePool.MessageDropException e) {
 *              //因意外消息池满而抛出该异常
 *              //TODO处理被丢弃的消息
 *          }
 *      }
 * });
 * //注册并阻塞线程, 等待消息返回
 * String message = null;
 * try {
 *      message = pool.registerAndWait(id, 5000);
 * } catch (PurposefulBlockingMessagePool.OutOfLimitException e) {
 *      //注册等待的消息超过限制
 *      //TODO异常处理,终止流程或抛出异常
 *      return;
 * } catch (PurposefulBlockingMessagePool.TimeoutException e) {
 *      //等待超时抛出该异常
 *      //TODO超时处理,终止流程或抛出异常
 *      return;
 * }
 * if (message == null){
 *      //TODO返回消息为空,终止流程或抛出异常
 *      return;
 * }
 *
 * }
* * @author S.Violet */ public class PurposefulBlockingMessagePool { /** * 默认限制 */ private static final int DEFAULT_LIMIT = 1000; /** * 意外消息池清理任务时延 */ private static final long UNEXPECTED_ITEM_FLUSH_DELAY = 10 * 1000000L; /** * 锁 */ private final ReentrantLock lock = new ReentrantLock(); /** * 信号池 */ private final Map conditionPool = new HashMap<>(); /** * 消息池 */ private final Map itemPool = new HashMap<>(); /** * 意外消息池(存放未注册ID的消息) */ private Map> unexpectedItemPool = null; /** * 意外消息有效期(ms) */ private long unexpectedItemValidityPeriod = 0; /** * 意外消息锁 */ private ReentrantLock unexpectedItemLock = null; /** * 意外消息池清理线程 */ private ExecutorService unexpectedItemFlushThreadPool = null; /** * 消息从意外消息池被丢弃回调 */ private MessageDropListener messageDropListener = null; /** * 注册等待数上限 */ private int registerLimit = DEFAULT_LIMIT; /** * 消息池内消息数上限 */ private int messageLimit = DEFAULT_LIMIT; /** * 直接丢弃意外消息(未注册ID的塞入消息) */ public PurposefulBlockingMessagePool(){ this(0, null); } /** * 意外消息(未注册ID的消息)塞入时, 存入意外消息池. 在意外消息过期前, 仍能被获取到, 在意外消息过期后, * 会被清理任务清理掉, 清理后将无法获得该消息. * * @param unexpectedItemValidityPeriod 意外消息有效期 ms >0生效 */ public PurposefulBlockingMessagePool(long unexpectedItemValidityPeriod){ this(unexpectedItemValidityPeriod, null); } /** * 意外消息(未注册ID的消息)塞入时, 存入意外消息池. 在意外消息过期前, 仍能被获取到, 在意外消息过期后, * 会被清理任务清理掉, 清理后将无法获得该消息. * * @param unexpectedItemValidityPeriod 意外消息有效期 ms >0生效 * @param messageDropListener 当消息从意外消息池被丢弃时回调该监听器 */ public PurposefulBlockingMessagePool(long unexpectedItemValidityPeriod, MessageDropListener messageDropListener){ this.unexpectedItemValidityPeriod = unexpectedItemValidityPeriod; setMessageDropListener(messageDropListener); if (this.unexpectedItemValidityPeriod > 0) { this.unexpectedItemLock = new ReentrantLock(); this.unexpectedItemFlushThreadPool = ThreadPoolExecutorUtils.createLazy(60L, "Thistle-PurposefulBlockMQ-Flush-%d"); this.unexpectedItemPool = new HashMap<>(); } } /** * @param messageDropListener 当消息从意外消息池被丢弃时回调该监听器 */ public void setMessageDropListener(MessageDropListener messageDropListener){ this.messageDropListener = messageDropListener; } /** * @param registerLimit 设置注册等待数上限, 注册等待的消息ID超过限制将会抛出异常, 默认1000 */ public void setRegisterLimit(int registerLimit){ this.registerLimit = registerLimit; } /** * @param messageLimit 意外消息数上限(不包括普通消息池), 超过上限将会抛弃塞入的新消息, 默认1000 */ public void setMessageLimit(int messageLimit){ this.messageLimit = messageLimit; } /** * 注册并阻塞等待消息, {@link PurposefulBlockingMessagePool#register(Object)}&{@link PurposefulBlockingMessagePool#wait(Object, long)} * @param id 指定的ID * @param timeout 超时时间 * @return 指定ID的目标对象(可能为空) * @exception OutOfLimitException 注册等待的消息数超过限制时抛出该异常, 注册被拒绝 * @exception TimeoutException 阻塞等待超时时抛出该异常 */ public I registerAndWait(K id, long timeout) throws OutOfLimitException, TimeoutException{ register(id); return wait(id, timeout); } /** * 注册指定ID, 表明需要目标对象, 注册后该消息池接受该ID目标对象的塞入(restock) * @param id 指定的ID * @exception OutOfLimitException 注册等待的消息数超过限制时抛出该异常, 注册被拒绝 */ public void register(K id) throws OutOfLimitException{ Condition condition = lock.newCondition(); try{ lock.lock(); if (getRegisterCount() > registerLimit){ throw new OutOfLimitException("[PurposefulBlockingMessagePool]register out of limit, drop this register : " + registerLimit); } conditionPool.put(id, condition); }finally { lock.unlock(); flush(); } } /** * 阻塞等待并返回指定ID的目标对象, 必须先调用{@link PurposefulBlockingMessagePool#register(Object)}注册等待的ID. * @param id 指定的ID * @param timeout 超时时间 * @return 指定ID的目标对象(可能为空) * @exception TimeoutException 阻塞等待超时时抛出该异常 */ public I wait(K id, long timeout) throws TimeoutException{ final long startMillis = System.currentTimeMillis(); try{ lock.lock(); final Condition condition = conditionPool.get(id); if (condition == null){ throw new RuntimeException("[PurposefulBlockingMessagePool]can't wait() before register()"); } I item; while ((item = getItem(id)) == null) { final long remainTimeout = timeout - (System.currentTimeMillis() - startMillis); if (remainTimeout <= 0){ break; } try { if (!condition.await(remainTimeout, TimeUnit.MILLISECONDS)) { break; } } catch (InterruptedException e) { break; } } getItem(id); conditionPool.remove(id); if (item == null){ throw new TimeoutException("[PurposefulBlockingMessagePool]waiting for message timeout : " + timeout); } return item; } finally { lock.unlock(); flush(); } } /** *

向消息池塞入指定ID的目标对象

* *

意外消息直接抛弃模式下, 若该ID未注册, 或等待已超时, 则塞入无效, 抛出异常

* *

意外消息池模式下, 若意外消息池消息数量超过限制, 则塞入无效, 抛出异常

* * @param id 指定ID * @param item 目标对象 * @exception MessageDropException 消息塞入失败, 被丢弃时抛出该异常. 意外消息直接丢弃模式下, 消息 * 塞入消息池时, 因ID未注册而丢弃消息. 意外消息池模式下, 因意外消息池超过数量限制而丢弃消息. 意外消息 * 池模式下, 因意外消息过期而丢弃消息的, 请使用messageDropListener监听事件. */ public void restock(K id, I item) throws MessageDropException { try{ lock.lock(); Condition condition = conditionPool.get(id); if (condition != null){ itemPool.put(id, item); condition.signalAll(); return; } }finally { lock.unlock(); flush(); } //意外消息存入意外消息池 if(unexpectedItemPool != null){ try{ unexpectedItemLock.lock(); if (getUnexpectedItemCount() > messageLimit){ throw new MessageDropException("[PurposefulBlockingMessagePool]unexpected message out of limit, drop this message, id:" + String.valueOf(id) + " limit:" + messageLimit); } //放入意外消息池 unexpectedItemPool.put(id, new UnexpectedItem<>(item)); return; } finally { unexpectedItemLock.unlock(); } } throw new MessageDropException("[PurposefulBlockingMessagePool]id not registered, drop this message, id:" + String.valueOf(id)); } /** * @return 注册的ID数 */ public int getRegisterCount(){ try{ lock.lock(); return conditionPool.size(); }finally { lock.unlock(); } } /** * @return 消息数(非意外消息) */ public int getItemCount(){ try{ lock.lock(); return itemPool.size(); }finally { lock.unlock(); } } /** * @return 意外消息数 */ public int getUnexpectedItemCount(){ if (unexpectedItemPool == null){ return 0; } try{ unexpectedItemLock.lock(); return unexpectedItemPool.size(); }finally { unexpectedItemLock.unlock(); } } /** * 立即启动清理任务, 清理意外消息池中的失效消息(过期) */ public void flush(){ if (unexpectedItemPool != null){ unexpectedItemFlushThreadPool.execute(new UnexpectedItemFlushTask()); } } private I getItem(K id){ I item = null; UnexpectedItem unexpectedItem = null; try { lock.lock(); item = itemPool.remove(id); }finally { lock.unlock(); } if (unexpectedItemPool != null) { try { unexpectedItemLock.lock(); unexpectedItem = unexpectedItemPool.remove(id); } finally { unexpectedItemLock.unlock(); } } if (item != null){ return item; } if (unexpectedItem != null){ return unexpectedItem.getItem(); } return null; } /** * 意外消息池清理任务 */ private class UnexpectedItemFlushTask implements Runnable{ @Override public void run() { //使清理任务间歇进行, 防止过多的占用锁 LockSupport.parkNanos(UNEXPECTED_ITEM_FLUSH_DELAY); List overdueItems = null; if (messageDropListener != null) { overdueItems = new ArrayList<>(); } try{ unexpectedItemLock.lock(); List overdueIds = new ArrayList<>(); for (Map.Entry> entry : unexpectedItemPool.entrySet()){ UnexpectedItem unexpectedItem = entry.getValue(); if (unexpectedItem == null || unexpectedItem.getItem() == null || unexpectedItem.isOverdue(unexpectedItemValidityPeriod)){ overdueIds.add(entry.getKey()); } } for (K id : overdueIds){ UnexpectedItem unexpectedItem = unexpectedItemPool.remove(id); if (overdueItems != null && unexpectedItem != null && unexpectedItem.getItem() != null){ overdueItems.add(unexpectedItem.getItem()); } } } finally { unexpectedItemLock.unlock(); } if (overdueItems != null) { for (I item : overdueItems) { messageDropListener.onDrop(item); } } } } /** * 意外消息 * @param 消息 */ private static class UnexpectedItem{ private I item; private long startTime; UnexpectedItem(I item){ this.startTime = System.currentTimeMillis(); this.item = item; } I getItem(){ return item; } /** * 消息是否过期 * @param unexpectedItemValidityPeriod 意外消息有效期 * @return true:过期 */ boolean isOverdue(long unexpectedItemValidityPeriod){ return (System.currentTimeMillis() - startTime) > unexpectedItemValidityPeriod; } } /** * 消息从意外消息池被抛弃时回调该监听器 * @param */ public interface MessageDropListener{ /** * 消息从意外消息池被抛弃时回调 * @param item 消息 */ void onDrop(I item); } /** *

意外消息直接抛弃模式下, 若该ID未注册, 或等待已超时, 则塞入无效, 抛出异常

* *

意外消息池模式下, 若意外消息池消息数量超过限制, 则塞入无效, 抛出异常

*/ public static class MessageDropException extends Exception{ public MessageDropException(String detailMessage) { super(detailMessage); } } /** * 注册等待消息数超出限制抛出异常 */ public static class OutOfLimitException extends Exception{ public OutOfLimitException(String detailMessage) { super(detailMessage); } } /** * 阻塞等待超时 */ public static class TimeoutException extends Exception{ public TimeoutException(String detailMessage) { super(detailMessage); } } }