目录
1. 什么是线程池
2. 为什么要使用线程池
3. 线程池创建
3.1 固定数量的线程池(Executors.newFixedThreadPool)
3.1.1 创建固定数量的线程池
3.1.2 线程池返回结果
3.1.3 submit() VS execut()
3.2.4 线程工厂
3.2 带缓存的线程池(Executors.newCachedThreadPool)
3.3 执行定时任务(Executors.newSingleThreadExecutor)
3.3.1 延迟执行(1次)
3.3.2 固定频率执行( scheduleAtFixedRate)
3.3.3 scheduleAtFixedRate VS scheduleWithFixedDelay
3.4 定时任务单线程(Executors.newSingleThreadScheduledExecutor)
3.5 单线程线程池(Executors.newSingleThreadExecutor)
3.6 根据当前CPU生成线程池(Executors.newWorkStealingPool)
3.7 手动方式(ThreadPoolExecutor)
3.7.1 创建忽略最新任务的线程池
3.7.2 ThreadPoolExecutor 参数说明
3.7.3 线程池执行流程
3.7.4 拒绝策略(5种(4(JDK提供的) + 1(自定义拒绝策略)))
4. 线程池状态
5. 究竟选用哪种线程池
线程池(ThreadPool)是⼀种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在⼀个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。
原因有以下几条:
阿里巴巴在其《Java开发手册》中也强制规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
线程池的创建方法总共有 7 种,总体来说可分为 2 类:
线程池的创建方式总共包含以下 7 种(其中前 6 种是通过 Executors 创建的,最后 1 种是通过 ThreadPoolExecutor 创建的):
示例代码:
public class ThreadPoolDemo1 {
public static void main(String[] args) {
// 1.创建一个包含5个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 2.使用线程池执行任务
for (int i = 0; i < 5; i++) {
// 给线程池添加任务
threadPool.submit(() -> System.out.println("线程名称:" + Thread.currentThread().getName()));
}
// 2.使用线程池执行任务2
for (int i = 0; i < 10; i++) {
// 给线程池添加任务
threadPool.execute(() -> System.out.println("线程名称:" + Thread.currentThread().getName()));
}
}
}
运行结果:
示例代码:
public class ThreadPoolDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
Future future = threadPool.submit(() -> {
int num = new Random().nextInt(100);
System.out.println("生成随机数:" + num);
return num;
});
System.out.println("得到线程池返回结果:" + future.get());
}
}
运行结果:
成功获得了线程池返回结果。
使用线程池执行任务有两种方式:submit() 和 execut() 。这两种方式的区别如下:
可以看到,使用 submit() 可以执行带有返回值的任务或者无返回值的任务,而 execut() 只能执行不带返回值的任务。
作用:为线程池提供现成的创建。
提供的功能:
示例代码:
public class ThreadPoolDemo3 {
public static void main(String[] args) {
// 1.创建线程工厂
ThreadFactory factory = r -> {
// 一定要把任务 Runnable 设置给新线程
Thread thread = new Thread(r);
// 设置线程的命名规则
thread.setName("我的线程:" + r.hashCode());
// 设置线程的优先级
thread.setPriority(Thread.MAX_PRIORITY);
return thread;
};
ExecutorService service = Executors.newFixedThreadPool(5, factory);
for (int i = 0; i < 5; i++) {
service.submit(() -> {
// 任务
Thread thread = Thread.currentThread();
System.out.println("线程池开始执行:" + thread.getName() + "线程池优先级:" + thread.getPriority());
});
}
}
}
运行结果:
线程池会根据任务数创建线程,并且在一定时间内可以重复使用这些线程。
示例代码:
public class ThreadPoolDemo4 {
public static void main(String[] args) {
// 创建线程池
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
int finalI = i;
service.submit(() -> System.out.println("i:" + finalI + " 线程名称:" + Thread.currentThread().getName()));
}
}
}
运行结果:
该方式适用于短时间有且有大量任务的场景,它的缺点是可能占用很多资源。
示例代码:
public class ThreadPoolDemo12 {
public static void main(String[] args) {
ScheduledExecutorService threadPool =
Executors.newScheduledThreadPool(10);
// 定时任务
System.out.println("设置定时任务:" + new Date());
// 延迟 n 秒后执⾏(只执⾏⼀次)
threadPool.schedule(() -> System.out.println("schedule:" + new Date()), 2, TimeUnit.SECONDS);
}
}
执行结果:
延迟 2s 后执行一次。
示例代码:
public class ThreadPoolDemo13 {
public static void main(String[] args) {
ScheduledExecutorService threadPool =
Executors.newScheduledThreadPool(10);
// 定时任务
System.out.println("设置定时任务:" + new Date());
threadPool.scheduleAtFixedRate(() ->
System.out.println("scheduleAtFixedRate:" + new Date()), 3, 2, TimeUnit.SECONDS);
}
}
运行结果:
延迟3s后执行,之后每2s执行一次。
参数解释:
scheduleAtFixedRate 是以上⼀次任务的开始时间,作为下次定时任务的参考时间的(参考时间+延迟任务=任务执行)。
示例代码:
public class ThreadPoolDemo5 {
public static void main(String[] args) {
// 创建线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
System.out.println("添加任务执行时间:" + LocalDateTime.now());
// 2s之后开始执行定时任务,定时任务每隔4s执行一次
service.scheduleAtFixedRate(() -> {
System.out.println("执行了任务:" + LocalDateTime.now());
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 2, 4, TimeUnit.SECONDS);
}
}
运行结果:
2s后开始执行定时任务,每隔5s执行一次。
设置的是每隔4秒执行一次定时任务,为什么实际上是5s执行一次呢?
注意,如果执行任务时间大于设置的定时任务执行时间,那么此方法会以执行任务的时间为准,简而言之,就是哪个时间长就以哪个时间作为定时任务执行的周期。
scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的。
示例代码:
public class ThreadPoolDemo5 {
public static void main(String[] args) {
// 创建线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
System.out.println("添加任务执行时间:" + LocalDateTime.now());
// 2s之后开始执行定时任务,每次执行间隔4秒
service.scheduleWithFixedDelay(() -> {
System.out.println("执行了任务:" + LocalDateTime.now());
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, 2, 4, TimeUnit.SECONDS);
}
}
运行结果:
2s后开始执行任务,每隔9秒执行一次定时任务。
为什么这个也不是每隔4s执行一次,而是9s呢???
因为 scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的,上个任务执行5s后,再延时4s执行延时任务。
示例代码:
public class ThreadPoolDemo6 {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
System.out.println("添加任务时间:" + LocalDateTime.now());
service.schedule(() -> System.out.println("执行任务:" + LocalDateTime.now()), 2, TimeUnit.SECONDS);
}
}
运行结果:
示例代码:
public class ThreadPoolDemo7 {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
int finalI = i;
service.submit(() -> System.out.println("任务:" + finalI + ", 线程名:" + Thread.currentThread().getName()));
}
}
}
运行结果:
单线程的线程池有什么意义呢?
示例代码:
public class ThreadPoolDemo8 {
public static void main(String[] args) {
ExecutorService service = Executors.newWorkStealingPool();
for (int i = 0; i < 100; i++) {
service.submit(() -> System.out.println("线程名:" + Thread.currentThread().getName()));
}
while (!service.isTerminated()){
}
}
}
运行结果:
示例代码:
public class ThreadPoolDemo11 {
public static void main(String[] args) {
ThreadFactory factory = r -> {
Thread thread = new Thread(r);
return thread;
};
// 手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(2), factory, new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 5; i++) {
int finalI = i;
executor.submit(() -> {
try {
Thread.sleep(finalI * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行任务" + finalI);
});
}
// 终止线程池
executor.shutdown();
}
}
运行结果:
JDK提供的四种拒绝策略:
自定义拒绝策略:
示例代码:
public class ThreadPoolDemo10 {
public static void main(String[] args) {
ThreadFactory factory = r -> {
Thread thread = new Thread(r);
return thread;
};
// 手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(2), factory,
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 自定义拒绝策略
System.out.println("自定义拒绝策略");
}
});
for (int i = 0; i < 5; i++) {
int finalI = i;
executor.submit(() -> {
try {
Thread.sleep(finalI * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行任务" + finalI);
});
}
// 终止线程池
executor.shutdown();
}
}
运行结果:
查看 ThreadPoolExecutor 源码可知线程的状态如下:
各个状态的转换过程有以下几种:
shutdown VS shutdownNow :
shutdown :
public class ThreadPoolDemo10 {
public static void main(String[] args) {
ThreadFactory factory = r -> {
Thread thread = new Thread(r);
return thread;
};
// 手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(2), factory,
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 自定义拒绝策略
System.out.println("自定义拒绝策略");
}
});
for (int i = 0; i < 5; i++) {
int finalI = i;
executor.submit(() -> {
try {
Thread.sleep(finalI * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行任务" + finalI);
});
}
// 终止线程池
executor.shutdown();
}
}
运行结果:
shutdownNow :
学习了这么多创建线程池的方式,究竟改用哪一种呢?
阿里巴巴《Java开发手册》给我们的答案:
所以综上情况所述,我们推荐使用 ThreadPoolExecutor 的方式进行线程池的创建,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避⼀些未知的风险。