Java语言虽然内置了多线程支持,启动一个新线程非常方便,但是,创建线程需要操作系统资源(线程资源,栈空间等),频繁创建和销毁大量线程需要消耗大量时间
如果复用一组线程,流程图如下:
把很多的小任务让一组线程来执行,而不是每个小任务对应一个新线程
这种能接收大量小任务并进行分发处理的就是线程池
假设没有线程池引发的后果:
线程池的主要思想:
总结:
线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待状态
如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理
线程池的优点:
线程池内线程的数量可以通过一些因素来估算(例如,系统CPU的数量,物理内存的大小,并发客户请求数量的期望值等)
更高级的线程池架构可以根据使用模式动态调整线程池内线程的数量,这类线程池架构再系统负荷低的时候,提供比较小的线程池,从而降低内存消耗(例如Apple的大中央空调)
Java标准库提供了 ExecutorService 接口表示线程池,用法如下:
Runnable task1 = null;
Runnable task2 = null;
Runnable task3 = null;
Runnable task4 = null;
Runnable task5 = null;
//创建固定大小的线程池
ExecutorService executorService=Executors.newFixedThreadPool(3);
//提交任务
executorService.submit(task1);
executorService.submit(task2);
executorService.submit(task3);
executorService.submit(task4);
executorService.submit(task5);
ExecutorService 只是接口,Java标准库提供的几个常用实现类有:
创建这些线程池的方法都被封装到Executors这个类中,以FixedThreadPool为例:
import java.util.concurrent.*;
public class Main {
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 0
start task 1
start task 2
start task 3
end task 0
start task 4
end task 1
start task 5
end task 2
end task 3
end task 4
end task 5
运行结果分析:一次性放入6个任务,由于线程池只有固定的4个线程,因此,前4个任务会同时执行,等到有线程空闲后,才会执行后面的两个任务。
线程池在程序结束的时候要关闭。使用shutdown()
方法关闭线程池的时候,它会等待正在执行的任务先完成,然后再关闭。shutdownNow()
会立刻停止正在执行的任务,awaitTermination()
则会等待指定的时间让线程池关闭。
如果我们把线程池改为CachedThreadPool
,由于这个线程池的实现会根据任务数量动态调整线程池的大小,所以6个任务可一次性全部同时执行。
如果我们想把线程池的大小限制在4~10个之间动态调整怎么办?
直接安排代码:
int min = 4;
int max = 10;
ExecutorService es = new ThreadPoolExecutor(min, max,
60L, TimeUnit.SECONDS, new SynchronousQueue());
如果我们想定时让线程跑任务怎么办?
Java标准库还提供了一个java.util.Timer
类,这个类也可以定期执行任务,但是,一个Timer
会对应一个Thread
,所以,一个Timer
只能定期执行一个任务,多个定时任务必须启动多个Timer
,而一个ScheduledThreadPool
就可以调度多个定时任务,所以,我们完全可以用ScheduledThreadPool
取代旧的Timer
JDK提供了ExecutorService
实现了线程池功能:
线程池内部维护一组线程,可以高效执行大量小任务
Executors
提供了静态方法创建不同类型的ExecutorService
必须调用shutdown()
关闭ExecutorService
ScheduledThreadPool
可以定期调度多个任务