目录
四、ScheduledThreadPool线程池:周期性和定时性
一、什么是线程池
虽然启动一个新线程很方便,但是创建线程需要操作系统资源,比如说线程资源或者栈的空间。频繁地创建和销毁大量的线程会消耗大量时间。所以我们可以把很多小任务让一组线程来执行,而不是一个任务对应一个新线程。
其实我觉得池的概念可以理解为“共享单车”的存在。
简单来说,线程池内部维护了很多个线程,没有任务的时候,线程都处于“待机状态”。如果有新任务,就分一个空闲线程去执行。
二、为什么要有线程池?还有什么池?
先抛池的概念去手动创建、释放管理线程,相比于创建线程的空间开销,为觉得多线程的上下文切换开销、同步开销和时间开销才是需要考虑的。
池的概念往往都是为了化零散为批处理,为了复用减少创建销毁的开销。
除了线程池,我还见过缓冲池、数据池(常量池)和内存池。
三、多线程学习:ExecutorServise接口
ExecutorService
是接口,其中提供的几个常用实现类有:
- FixedThreadPool:线程数固定的线程池;
- CachedThreadPool:线程数根据任务动态调整的线程池;
- SingleThreadExecutor:仅单线程执行的线程池。
这些类的前面加个new就是创建相对应线程池的方法了,创建这些线程池的方法都在Executors类中:
经典用法:
ExecutorServise executor = Executors.newFixedThreadPool(3); //固定线程数
//提交任务
executor.submit(task1);
executor.submit(task2);
1、线程池的关闭
shutdown():等待正在执行的任务完成后,再关闭
shutdownNow():立刻停止正在执行的任务
awaitTermination():等待指定的时间让线程池关闭
2、FixedThreadPool执行逻辑
public class ThreadPool2 {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(4); // 固定大小线程池
for (int i = 0; i < 6; i++) {
es.submit(new Task("" + i));
}
es.shutdown(); // 关闭线程池
}
}
class Task implements Runnable {
private final String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("start task " + name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("end task " + name);
}
}
/*
start task 1
start task 2
start task 0
start task 3
end task 0
end task 2
end task 1
end task 3
start task 4
start task 5
end task 4
end task 5
*/
上面的代码我们一次性放入了6个任务,但是线程池只有固定4个任务。所以执行逻辑是:前四个任务同时执行,等有线程空闲后再继续执行接下来的。
3、CachedThreadPool执行逻辑
这样的线程池可以根据任务数量调整线程池大小,所以6个任务可以一次性全部同时执行。
如果我们想把线程池大小限制在4-10个动态范围,我们可以:
int min = 4;
int max = 10;
ExecutorService es = new ThreadPoolExecutor(min, max,
60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); //这部分都是源码照抄
四、ScheduledThreadPool线程池:周期性和定时性
ScheduledThreadPool是一个能够实现周期性和定时性任务的线程池。
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。Executor是一个大接口,用于显性创建线程:
1、定时性:schedule方法
ScheduledExecutorServise接口中的方法schedule用于自动完成特定任务,其中的参数列表中可以设置线程的执行时间:
public class TestScheduledThreadPool {
//格式化
static SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//AtomicInteger用来计数
static AtomicInteger number = new AtomicInteger();
public static void main(String[] args) throws Exception {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
for (int i = 0; i < 3; i++){
executorService.schedule(new Runnable(){
@Override
public void run(){
System.out.println("第" + number.incrementAndGet() + "周期线程运行当前时间【" + sim.format(new Date()) + "】");
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
},3L,TimeUnit.SECONDS);
}
System.out.println("主线程运行当前时间【" + sim.format(new Date()) + "】");
}
}
/*
主线程运行当前时间【2022-06-08 18:38:03】
第1周期线程运行当前时间【2022-06-08 18:38:06】
第2周期线程运行当前时间【2022-06-08 18:38:06】
第3周期线程运行当前时间【2022-06-08 18:38:09】
*/
可以看到周期线程是在主线程时间3s后执行的因为周期线程上来就等待三秒(schedule)。由于核心线程只设置了2个,所以第三个任务一样需要等待3秒后,有了空闲线程才能执行。之后的3秒都是线程任务里设置的睡眠时间(sleep)。
2、周期性:scheduleAtFixedRate方法
scheduleAtFixedRate方法提供周期执行任务的操作。和schedule方法很类似,上述代码只需要将参数“3L”改为“3,5”,周期线程就会以2s的间隔执行下去:
更多方法可以参考此博客: 深入理解Java线程池:ScheduledThreadPoolExecutor | Idea Buffer