DiscardPolicy(丢弃策略)1 public static class DiscardPolicy implements RejectedExecutionHandler {23 public DiscardPolicy { }45 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {6 }7 }功能:直接静悄悄的丢弃这个任务 , 不触发任何动作
使用场景:如果你提交的任务无关紧要 , 你就可以使用它。因为它就是个空实现 , 会悄无声息的吞噬你的的任务 。所以这个策略基本上不用了
DiscardOldestPolicy(弃老策略) 1 public static class DiscardOldestPolicy implements RejectedExecutionHandler {23 public DiscardOldestPolicy { }45 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {6 if (!e.isShutdown) {7 e.getQueue.poll;8 e.execute(r);9 }10 }11 }功能:如果线程池未关闭 , 就弹出队列头部的元素 , 然后尝试执行
使用场景:这个策略还是会丢弃任务 , 丢弃时也是毫无声息 , 但是特点是丢弃的是老的未执行的任务 , 而且是待执行优先级较高的任务 。基于这个特性 , 我能想到的场景就是 , 发布消息 , 和修改消息 , 当消息发布出去后 , 还未执行 , 此时更新的消息又来了 , 这个时候未执行的消息的版本比现在提交的消息版本要低就可以被丢弃了 。因为队列中还有可能存在消息版本更低的消息会排队执行 , 所以在真正处理消息的时候一定要做好消息的版本比较
第三方实现的拒绝策略Dubbo 中的线程拒绝策略 1public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {23 protected static final Logger logger = LoggerFactory.getLogger(AbortPolicyWithReport.class);45 private final String threadName;67 private final URL url;89 private static volatile long lastPrintTime = 0;1011 private static Semaphore guard = new Semaphore(1);1213 public AbortPolicyWithReport(String threadName, URL url) {14 this.threadName = threadName;15 this.url = url;16 }1718 @Override19 public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {20 String msg = String.format("Thread pool is EXHAUSTED!" +21 " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d)," +22 " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",23 threadName, e.getPoolSize, e.getActiveCount, e.getCorePoolSize, e.getMaximumPoolSize, e.getLargestPoolSize,24 e.getTaskCount, e.getCompletedTaskCount, e.isShutdown, e.isTerminated, e.isTerminating,25 url.getProtocol, url.getIp, url.getPort);26 logger.warn(msg);27 dumpJStack;28 throw new RejectedExecutionException(msg);29 }3031 private void dumpJStack {32 //省略实现33 }34}可以看到 , 当dubbo的工作线程触发了线程拒绝后 , 主要做了三个事情 , 原则就是尽量让使用者清楚触发线程拒绝策略的真实原因
- 输出了一条警告级别的日志 , 日志内容为线程池的详细设置参数 , 以及线程池当前的状态 , 还有当前拒绝任务的一些详细信息 。可以说 , 这条日志 , 使用dubbo的有过生产运维经验的或多或少是见过的 , 这个日志简直就是日志打印的典范 , 其他的日志打印的典范还有spring 。得益于这么详细的日志 , 可以很容易定位到问题所在
- 输出当前线程堆栈详情 , 这个太有用了 , 当你通过上面的日志信息还不能定位问题时 , 案发现场的dump线程上下文信息就是你发现问题的救命稻草 , 这个可以参考《dubbo线程池耗尽事件-"CyclicBarrier惹的祸"》
- 继续抛出拒绝执行异常 , 使本次任务失败 , 这个继承了JDK默认拒绝策略的特性
Netty 中的线程池拒绝策略
1 private static final class NewThreadRunsPolicy implements RejectedExecutionHandler {2 NewThreadRunsPolicy {3 super;4 }56 public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {7 try {8 final Thread t = new Thread(r, "Temporary task executor");9 t.start;10 } catch (Throwable e) {11 throw new RejectedExecutionException(12 "Failed to start a new thread", e);13 }14 }15 }Netty 中的实现很像 JDK 中的 CallerRunsPolicy , 舍不得丢弃任务 。不同的是 , CallerRunsPolicy 是直接在调用者线程执行的任务 。而 Netty是新建了一个线程来处理的 。所以 , Netty的实现相较于调用者执行策略的使用面就可以扩展到支持高效率高性能的场景了 。但是也要注意一点 , Netty的实现里 , 在创建线程时未做任何的判断约束 , 也就是说只要系统还有资源就会创建新的线程来处理 , 直到new不出新的线程了 , 才会抛创建线程失败的异常
推荐阅读
- B-Tree 数据结构详解及Java代码实现
- Java序列化是什么?你知道什么时候需要序列化吗?
- 锦鲤池长绿藻怎么处理 锦鲤在有绿藻的水里容易死吗
- 池宗宪:现代茶席的布置
- 手机快充会影响电池寿命吗?
- 不论你用什么品牌的手机,关闭这些按钮,手机电池更耐用
- 如何保护你的iPhone电池
- 连续充电一夜不拔,对手机电池会有伤害吗?
- Java Overload 与 Override 差别
- 详解Java多线程锁之Lock和ReadWriteLock
