线程池使
java中经常需要用到多线程来处理一些业务,我们非常不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。java中涉及到线程池的相关类均在jdk1.5开始的java.util.concurrent包中,
涉及到的几个核心类及接口包括:
Executor、
Executors、
ExecutorService、
ThreadPoolExecutor、
FutureTask、
Callable、
Runnable等。
Executors工具类
创建线程池
方法名 | 功能 |
new ThreadPool(int nThreads) | 创建固定大小的线程池 |
newSingleThreadExecutor() | 创建只有一个线程的线程池 |
newCachedThreadPool() | 创建一个不限线程数上限的线程池,任何提交的任务都将立即执行 |
线程池原理
拒绝策略
- AbortPolicy(默认拒绝策略)直接抛出异常
- CallerRunsPolicy 哪儿来的回哪儿去,那个线程让你来的由那个线程执行,反正我线程池是不执行
- DiscardPolicy(忽略新任务)
- DiscardOldestPolicy(弃老策略)
-
JDK 内置的拒绝策略如下:
1.AbortPolicy : 直接抛出异常,阻止系统正常运行。
2.CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
3.DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。
4.DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际需要,完全可以自己扩展 RejectedExecutionHandler 接口。
任务调度
Timer
Timer 实现定时功能,Timer 的优点在于简单易用,
但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,
同一时间只能有一个任务在执行,
前一个任务的延迟或异常都将会影响到之后的任务
如果任务1出现异常任务2就无法执行
private static void method1() {
Timer timer = new Timer();
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("task 1");
//int i = 1 / 0;//任务一的出错会导致任务二无法执行
Thread.sleep(2000);
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("task 2");
}
};
// 使用 timer 添加两个任务,希望它们都在 1s 后执行
// 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此任务1的延时,影响了任务2的执行
timer.schedule(task1, 1000);//17:45:56 c.ThreadPool [Timer-0] - task 1
timer.schedule(task2, 1000);//17:45:58 c.ThreadPool [Timer-0] - task 2
}
任务调度线程池 ScheduledThreadPoolExecutor 继承 ThreadPoolExecutor:
- 使用内部类 ScheduledFutureTask 封装任务
- 使用内部类 DelayedWorkQueue 作为线程池队列
- 重写 onShutdown 方法去处理 shutdown 后的任务
- 提供 decorateTask 方法作为 ScheduledFutureTask 的修饰方法,以便开发者进行扩展
构造方法:Executors.newScheduledThreadPool(int corePoolSize)
public ScheduledThreadPoolExecutor(int corePoolSize) {
// 最大线程数固定为 Integer.MAX_VALUE,保活时间 keepAliveTime 固定为 0
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
// 阻塞队列是 DelayedWorkQueue
new DelayedWorkQueue());
}
常用 API:
ScheduledFuture<?> schedule(Runnable/Callable<V>, long delay, TimeUnit u)
:延迟执行任务ScheduledFuture<?> scheduleAtFixedRate(Runnable/Callable<V>, long initialDelay, long period, TimeUnit unit)
:定时执行周期任务,不考虑执行的耗时,参数为初始延迟时间、间隔时间、单位ScheduledFuture<?> scheduleWithFixedDelay(Runnable/Callable<V>, long initialDelay, long delay, TimeUnit unit)
:定时执行周期任务,考虑执行的耗时,参数为初始延迟时间、间隔时间、单位
基本使用:
-
1.延迟任务,
-
但是出现异常并不会在控制台打印,也不会影响其他线程的执行
public static void main(String[] args){
// 线程池大小为1时也是串行执行
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// 添加两个任务,都在 1s 后同时执行
executor.schedule(() -> {
System.out.println("任务1,执行时间:" + new Date());
//int i = 1 / 0;
try { Thread.sleep(2000); } catch (InterruptedException e) { }
}, 1000, TimeUnit.MILLISECONDS);
executor.schedule(() -> {
System.out.println("任务2,执行时间:" + new Date());
}, 1000, TimeUnit.MILLISECONDS);
}
2. 定时任务 scheduleAtFixedRate:
一次任务的启动到下一次任务的启动之间只要大于等于间隔时间,抢占到 CPU 就会立即执行
任务执行时间大于period 以任务执行时间为间隔,所以是每隔2秒
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
System.out.println("start..." + new Date());
pool.scheduleAtFixedRate(() -> {
System.out.println("running..." + new Date());
Thread.sleep(2000);
}, 1, 1, TimeUnit.SECONDS);
}
/*start...Sat Apr 24 18:08:12 CST 2021
running...Sat Apr 24 18:08:13 CST 2021
running...Sat Apr 24 18:08:15 CST 2021
running...Sat Apr 24 18:08:17 CST 2021
3.定时任务 scheduleWithFixedDelay:
一次任务的结束到下一次任务的启动之间等于间隔时间,抢占到 CPU 就会立即执行,这个方法才是真正的设置两个任务之间的间隔
第一个任务执行完毕后,在等1秒才会执行, 所示是3秒
public static void main(String[] args){
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
System.out.println("start..." + new Date());
pool.scheduleWithFixedDelay(() -> {
System.out.println("running..." + new Date());
Thread.sleep(2000);
}, 1, 1, TimeUnit.SECONDS);
}
/*start...Sat Apr 24 18:11:41 CST 2021
running...Sat Apr 24 18:11:42 CST 2021
running...Sat Apr 24 18:11:45 CST 2021
running...Sat Apr 24 18:11:48 CST 2021