1. 线程池概述
1.降低资源消耗,减少线程反复创建和销毁的资源浪费
2.提高响应速度,当线程池中有空闲线程时,任务能立即执行
3.控制线程数量,每一个线程都占用内存空间,线程池可以通过控制和管理线程数,避免内存消耗过大
2. 线程池使用
2.1 创建线程池
通常使用ThreadPoolExecutor
类手动创建线程池
private void createThreadPoolExecutor(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2, //核心线程数
5, //最大线程数
2, //新开线程的存活时间
TimeUnit.SECONDS, //存活时间的单位
new LinkedBlockingQueue<>(2), //等待队列 可指定容量,未指定时为无界队列
Executors.defaultThreadFactory(), //线程工厂 默认就是Executors.defaultThreadFactory()
(r, executor) -> System.err.println("有任务被拒绝执行了") //拒绝策略 默认抛出RejectedExecutionException异常
);
}
注1:ThreadPoolExecutor
类的构造方法有四种,上述构造方法为了解释全部参数,用了最全的一种,其他三种构造方法分别是在上述入参中不传线程工厂或拒绝策略或都不传,详细信息可去该类中查看。
注2:Executors
类中有很多静态方法可以快速创建线程池,可看情况使用
2.2 线程池使用与分析
- 情景一分析:当线程池的等待队列为无界队列时,超出核心线程数的线程都进入了等待队列
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Description 线程池的使用
* @Author lin
* @Date 2021/3/3 19:50
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) throws InterruptedException {
//线程池信息:核心线程为2,最大线程为3,新开线程存活3秒,无界队列
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 3, 3, TimeUnit.SECONDS,
new LinkedBlockingQueue<>());
for (int i = 1; i <= 4; i++) {
int n = i;
threadPoolExecutor.submit(() -> { //用submit提交任务
try {
System.out.println("开始执行:" + n);
Thread.sleep(2000L);
System.out.println("执行结束:" + n);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("任务提交成功 :" + i);
}
// 查看线程数量,查看队列等待数量
Thread.sleep(1000L);
System.out.println("当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
System.out.println("当前线程池等待的数量为:" + threadPoolExecutor.getQueue().size());
// 等所有任务结束时,查看线程数量,查看队列等待数量
Thread.sleep(5000L);
System.out.println("当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
System.out.println("当前线程池等待的数量为:" + threadPoolExecutor.getQueue().size());
}
}
//执行结果:
//任务提交成功 :1
//任务提交成功 :2
//开始执行:1
//任务提交成功 :3
//任务提交成功 :4
//开始执行:2
//当前线程池线程数量为:2
//当前线程池等待的数量为:2
//执行结束:1
//开始执行:3
//执行结束:2
//开始执行:4
//执行结束:3
//执行结束:4
//当前线程池线程数量为:2
//当前线程池等待的数量为:0
- 情景二分析:将线程池的最大线程数设为2,等待队列容量设为1,可以发现当任务数超过了
核心线程数与等待队列容量之和
,但线程数少于最大线程数
时,新开了一个线程,在任务全部执行结束后,该线程在存活时间后被销毁
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Description 线程池的使用
* @Author lin
* @Date 2021/3/3 19:50
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) throws InterruptedException {
//线程池信息:核心线程为2,最大线程为3,新开线程存活3秒,队列容量为1
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 3, 3, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1));
for (int i = 1; i <= 4; i++) {
int n = i;
threadPoolExecutor.submit(() -> { //用submit提交任务
try {
System.out.println("开始执行:" + n);
Thread.sleep(2000L);
System.out.println("执行结束:" + n);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("任务提交成功 :" + i);
}
// 查看线程数量,查看队列等待数量
Thread.sleep(1000L);
System.out.println("当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
System.out.println("当前线程池等待的数量为:" + threadPoolExecutor.getQueue().size());
// 等所有任务结束时,查看线程数量,查看队列等待数量
Thread.sleep(5000L);
System.out.println("当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
System.out.println("当前线程池等待的数量为:" + threadPoolExecutor.getQueue().size());
}
}
//执行结果:
//任务提交成功 :1
//任务提交成功 :2
//开始执行:1
//任务提交成功 :3
//开始执行:2
//任务提交成功 :4
//开始执行:4
//当前线程池线程数量为:3
//当前线程池等待的数量为:1
//执行结束:1
//执行结束:2
//执行结束:4
//开始执行:3
//执行结束:3
//当前线程池线程数量为:2
//当前线程池等待的数量为:0
- 情景三分析:将线程池的最大线程数设为2,等待队列容量设为1,可以发现当任务数超过了
核心线程数与等待队列容量之和
,但线程数等于最大线程数
时,新的任务被拒绝
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Description 线程池的使用
* @Author lin
* @Date 2021/3/3 19:50
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) throws InterruptedException {
//线程池信息:核心线程为2,最大线程为2,新开线程存活3秒,队列容量为1
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2, 3, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1), (r, executor) -> System.err.println("有任务被拒绝执行了"));
for (int i = 1; i <= 4; i++) {
int n = i;
threadPoolExecutor.submit(() -> { //用submit提交任务
try {
System.out.println("开始执行:" + n);
Thread.sleep(2000L);
System.out.println("执行结束:" + n);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println("任务提交成功 :" + i);
}
// 查看线程数量,查看队列等待数量
Thread.sleep(1000L);
System.out.println("当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
System.out.println("当前线程池等待的数量为:" + threadPoolExecutor.getQueue().size());
// 等所有任务结束时,查看线程数量,查看队列等待数量
Thread.sleep(5000L);
System.out.println("当前线程池线程数量为:" + threadPoolExecutor.getPoolSize());
System.out.println("当前线程池等待的数量为:" + threadPoolExecutor.getQueue().size());
}
}
//执行结果:
//任务提交成功 :1
//任务提交成功 :2
//开始执行:1
//任务提交成功 :3
//有任务被拒绝执行了
//任务提交成功 :4
//开始执行:2
//当前线程池线程数量为:2
//当前线程池等待的数量为:1
//执行结束:1
//执行结束:2
//开始执行:3
//执行结束:3
//当前线程池线程数量为:2
//当前线程池等待的数量为:0
2.3 一些特殊线程池
- 缓存线程池:核心线程数为0,最大线程数为线程最大值,无界队列。这种线程池适用于任务数量不确定的情况,最好使用方式一指定最大线程数
private void newCachedThreadPool() {
//以下两种方式创建方式效果一样
new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); //方式1
Executors.newCachedThreadPool(); //方式2
}
- 定时执行线程池
import java.util.concurrent.*;
/**
* @Description 定时执行线程池
* @Author lin
* @Date 2021/3/3 19:50
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
new ThreadPoolExecutorDemo().threadPoolExecutorTest1();
new ThreadPoolExecutorDemo().threadPoolExecutorTest2();
}
/**
* 任务提交3秒后执行
*/
private void threadPoolExecutorTest1() {
//和Executors.newScheduledThreadPool()一样
ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
threadPoolExecutor.schedule(() -> System.out.println("一次性任务执行时间:" + System.currentTimeMillis()), 3000L, TimeUnit.MILLISECONDS);
System.out.println("一次性任务提交时间:" + System.currentTimeMillis());
}
/**
* 定时任务
*/
private void threadPoolExecutorTest2() {
long currentTimeMillis = System.currentTimeMillis();
ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
//当运行周期大于周期间隔时,下一个任务会立即执行
threadPoolExecutor.scheduleAtFixedRate(() -> {
try {
Thread.sleep(3000L);//运行时间3s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("定时任务1执行时间:" + (System.currentTimeMillis() - currentTimeMillis));
}, 2000L, 1000L, TimeUnit.MILLISECONDS); //周期间隔1s
//无论周期时长如何,下一个任务都会执行延时
threadPoolExecutor.scheduleWithFixedDelay(() -> {
try {
Thread.sleep(3000L);//运行时间3s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("定时任务2执行时间:" + (System.currentTimeMillis() - currentTimeMillis));
}, 2000L, 1000L, TimeUnit.MILLISECONDS); //延时1s
}
}
//执行结果:任务1的时间间隔约为3s,任务2的时间间隔约为4s
//一次性任务提交时间:1614776434219
//一次性任务执行时间:1614776437219
//定时任务2执行时间:5004
//定时任务1执行时间:5004
//定时任务1执行时间:8004
//定时任务2执行时间:9005
//定时任务1执行时间:11005
//定时任务2执行时间:13006
2.4 关闭线程池
import java.util.concurrent.*;
/**
* @Description 关闭线程池
* @Author lin
* @Date 2021/3/3 19:50
*/
public class ThreadPoolExecutorDemo {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 3, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3), (r, executor) -> System.err.println("有任务被拒绝执行了"));
for (int i = 1; i <= 2; i++) {
int n = i;
threadPoolExecutor.submit(() -> {
try {
System.out.println("开始执行:" + n);
Thread.sleep(3000L);
System.err.println("执行结束:" + n);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
});
System.out.println("任务提交成功 :" + i);
}
// 1秒后终止线程池
Thread.sleep(1000L);
threadPoolExecutor.shutdown(); //关闭后已提交任务继续执行结束
//threadPoolExecutor.shutdownNow(); //关闭后已提交任务被打断 sleep interrupted
// 线程池关闭后,再提交线程将被拒绝
threadPoolExecutor.submit(() -> System.out.println("追加一个任务"));
}
}
//shutdown执行结果:
//任务提交成功 :1
//开始执行:1
//任务提交成功 :2
//开始执行:2
//有任务被拒绝执行了
//执行结束:2
//执行结束:1
//shutdownNow执行结果:
//任务提交成功 :1
//任务提交成功 :2
//开始执行:1
//开始执行:2
//sleep interrupted
//sleep interrupted
//有任务被拒绝执行了