为了看一下拒绝策略的拒绝效果是什么样的
,我通过匿名内部类的方式自定义了一个拒绝策略,并实现了其中的拒绝方法rejectedExecution(),而对于其中固定的参数,是否可以通过转换获取到真实的Runnable实现类的实例呢
? 答案是不能的。
接下来我们一起验证一下,同时也看下具体,线程池提交一些任务,哪个任务会进队列,哪个会被核心线程直接执行,哪个又会被拒绝
说明:因为每个子任务需要执行的业务逻辑没有,我通过sleep()来模拟实际业务花费的时间;
下面的具体的代码:
package com.atguigu.gulimall.providerconsumer.test;
import java.util.concurrent.*;
/**
*
* 验证拒绝策略的拒绝效果
* @author: jd
* @create: 2024-07-17
*/
public class RejectThreadPoolDemo {
String diuqinme = "";
public static void main(String[] args) throws InterruptedException {
ExecutorService es = new ThreadPoolExecutor(1, 2, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1), Executors.defaultThreadFactory(), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if(r instanceof MyTask){
System.out.println("r 是 MyTask的实例");
MyTask r1 = (MyTask) r;
System.out.println("队列已满,任务名称 :"+r1.toString() + "被丢弃;");
}else{
System.out.println("(r instanceof MyTask) = " + (r instanceof MyTask));
System.out.println("队列已满,任务名称被丢弃 (这里还得验证怎么获取子任务的name);");
}
}
});
for (int i = 0; i < 4; i++) {
MyTask task = new MyTask("name-" + i);
System.out.println("准备提交任务,任务名称 = " + task.toString());
es.submit(task);
Thread.sleep(1000); //提交完一个任务之后,先睡眠一秒,再提交下面一个; 为了看见效果清晰,所以都是过一秒提交再进行提交
}
es.shutdown();
}
public static class MyTask implements Runnable {
String name;
MyTask(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
@Override
public void run() {
System.out.println(System.currentTimeMillis() + "正在执行的任务name :" + this.name);
try {
//每个任务睡眠10s模拟业务逻辑花费的时间
Thread.sleep(10000);
System.out.println("正在执行的任务name :" + this.name+"执行结束");
} catch (InterruptedException e) {
System.out.println(e);
}
}
}
}
解释一下,我线程池的创建参数: 核心线程 1个, 最大线程数 2个,没任务之后,存活时间0s(非核心线程直接关闭),消息队列我用的是有界队列,队列容量为1,拒绝策略 是自定义的一个拒绝策略,并重写了他的拒绝动作(为了让他在拒绝后不直接跑异常,而是提示一下,这样其他的业务可以继续执行。
特别注意哦~ 当然这是在自己玩的过程中,实际的生产中 对于被拒绝的任务或者数据都需要记录下来,或者队列采用无界队列、有固定参数的有界队列。线程数设置的稍微大一些。否则任务的直接丢失会造成一些比较严重的问题,可以将日志信息记录的更详细一点,来分析系统的负载和任务丢失的情况。)
参数设置可见我的另一篇博文:【多线程】线程池的最优参数设置,如何考虑?
上面代码的实验效果
效果1:
我提交了四个任务,而且每个任务的执行时间较长(我这里设置了每个任务执行10s),所以这就导致只要是我提交到线程池中的任务,用完线程池中的线程都不会立刻释放,因为他还没执行完,所以还要占用这,
解释一下现象:让我们更清晰一些
首先,准备提交任务名称name-0 然后直接被线程池中的核心线程执行了,所以可见到第二行,name-0正在执行。
然后,又每隔1s提交了,任务name-1 name-2 ,然后为什么任务name-1没有直接被启动的非核心线程来执行呢 ?我猜测是非核心线程最初没启动着 ,是关闭着的,所以当name-1被提交的时候,只有一个线程,而且这个线程被name-0占用着,所以name-1被扔到队列中了,队列容量=1,所以此时队列被占满了。
然后,提交了任务name-2,此时非核心线程启动了,正好name-2赶上了,所以name-2成功提交,打印出了开始执行。
然后我又间隔1 s ,提交了name-3 ,这个是第四个想要提交的任务,因为我是每个1s提交一个任务的,但是每个任务的运行时间是10s。所以第4秒提交完name-3之后,所,有的线程(1个核心【在执行name-0】+1个非核心【在执行name-2】),队列中有一个name-1在排队,所以此时name-3就没地方去了,就被丢弃了,所以是这个任务触发了拒绝策略,但我本想着在拒绝策略中的rejectedExecution()来打印出来拒绝了哪个任务,
但是我尝试了很多办法都没有获取到提交的任务的实现类实例【我查了资料,如下】
在 Java 的 RejectedExecutionHandler 接口中,rejectedExecution 方法的参数 Runnable r 之所以不是你具体提交的 Runnable 实现类的直接类型,主要是因为 Java 的类型系统和接口的设计方式。
当你向 ThreadPoolExecutor 或其他实现了 ExecutorService 接口的线程池提交一个任务时,这个任务是通过
Runnable 或 Callable 接口的实例来提交的。这两个接口是 Java 并发框架中的核心接口,用于定义可以被线程执行的任务。
当你定义一个具体的 Runnable 实现类时,你实际上是在实现一个通用的任务执行接口。这个接口规定了任务应该如何被执行(通过 run
方法),但它并不关心任务的具体实现细节或类型。因此,当你将这个实现类的实例提交给线程池时,线程池只看到它是一个 Runnable(或 Callable),而不知道它背后的具体实现类是什么。当线程池因为某些原因(如线程池关闭、线程池已满且工作队列也满等)无法执行这个任务时,它会调用你提供的
RejectedExecutionHandler 的 rejectedExecution 方法,并将被拒绝的任务(作为 Runnable
类型的引用)和线程池本身作为参数传递给这个方法。在这个上下文中,Runnable r 是你提交的任务的引用,但它被当作 Runnable 接口的实例来处理,而不是你具体的实现类。这是因为
RejectedExecutionHandler 接口是设计为通用的,可以处理任何实现了 Runnable接口的任务,而不需要知道这些任务的具体类型。
文献中说可以通过进行类型检查和转换,r instanceof MyTask ,但是我实验的效果:拒绝策略中得到的不是提交任务(Runnable的实现类)的实例。只是一个Runnable对象。所以我还没找到什么方式可以获取到具体任务的name
**接着说效果:**再过几秒,被提交的任务到10s后就结束了,所以name-0执行结束,然后在核心线程空出来了,所以在队列中的name-1被提交,然后紧接着name-2执行结束,然后最后是name-1,这样就执行完毕了。
效果2:
其他参数不变,我调整有界队列的长度 从1改成了2,,因为我只是提交了4个任务,所以总共下来的都够用的,都被执行成功了。
OK,到这里就完事了,现象演示结束啦,而且疑问也验证出来了。但是也留下了一个疑问,拒绝策略中是否能获取到提交的任务实现类的name,我会继续验证,大佬们有清楚的,积极分享评论哦,谢谢。
码字不易,还望大家多多点赞,支持一下博主,Thanks♪(・ω・)ノ。