池化技术的出现是为了优化资源的使用,常见的池化技术有:线程池、JDBC连接池、内存池、对象池……
线程池的好处有:线程复用,管理线程,控制最大并发数
(假设线程池有5个线程,那么最大并发数为5,当然,还要考虑电脑的CPU是几核的)
传统方式创建线程
下面的代码例子是使用传统的方式创建10个线程,这样做的弊端是频繁的创建线程和销毁线程会导致非常大的系统开销;从代码的运行结果也可以看到每个线程都是不一样的,也就是线程用完即销毁,用时再创建,并没有实现复用
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + ":ok");
}).start();
}
}
线程池的三大方法
// 1. 单个线程,也就是创建的线程池中只有一个线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 2. 创建一个指定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 3. 创建一个可伸缩的线程池,遇强则强,遇弱则弱;也就是我们需要用到多少个线程,这个线程池就有多少个线程
ExecutorService threadPool = Executors.newCachedThreadPool();
看第一个方法:
由于创建的线程池中只有一个线程,所以支持的最大并发数为1,因为每次都得等待上一个线程执行完毕后,归还到线程池,别的线程才有机会获取执行,由代码的运行结果也可以看到,每次执行的线程都是同一个
public static void main(String[] args) {
// 单个线程,也就是创建的线程池中只有一个线程
ExecutorService threadPool = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + ":ok");
});
}
// 关闭线程池
threadPool.shutdown();
}
看第二方法:
创建了一个指定大小为5的线程池,所以支持的最大并发数为5,也就是如果第一个线程在执行,又一个线程要执行,无需等待上一个线程执行完毕,可以直接从线程池中再拿一个线程运行,也就是多个线程可以同时运行;由代码的运行结果也可以看到,线程池中的5个线程都可能执行
public static void main(String[] args) {
// 创建一个大小为5的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + ":ok");
});
}
// 关闭线程池
threadPool.shutdown();
}
看第三方法:
创建了一个可伸缩的线程池,线程池的大小会根据我们的需要用到的线程池数量去创建,由代码的运行结果也可以看到,确实使用了多个不同的线程,其中有三个线程使用的是同一个,这种现象是因为线程执行完毕后归还线程池,下一线程可能获取它再次执行
public static void main(String[] args) {
// 创建一个可伸缩的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + ":ok");
});
}
// 关闭线程池
threadPool.shutdown();
}
线程池的七大参数
在了解线程池的七大参数之前,我们先看一下《阿里巴巴Java开发手册》对于创建线程池的规范约束:
前面我们讲的三大方法都是使用了Executors这个工具类去创建线程的,但是阿里巴巴规范建议我们不这么做,所以我们应该使用原生的ThreadPoolExecutor去自定义创建线程,构造方法需要传递七个参数,这就是线程池的七大参数,追踪源码,其实线程池的三大方法最终也是去new ThreadPoolExecutor,只不过七个参数中有些是使用默认的
通过银行办理业务理解七大参数:
对于银行办理业务,假设默认工作的窗口只有两个,这对应七大参数中的核心线程数为2;当两个人在办理业务,有第三个人进来了,那么它会进入阻塞队列,假设阻塞队列的长度为3,那么当阻塞队列满的时候,就会触发3 4 5这个三个窗口进行工作,也就是会触发七大参数中的非核心线程(非核心线程=最大线程数-核心线程数),当银行的五个窗口都满了,并且阻塞队列也满了,这时还有人进来的办理业务的话,就会采取拒绝策略,也就是对应七大参数中当所需要的线程数>最大线程数+阻塞队列的长度,这时候会触发拒绝策略,这里我们使用的是AbortPolicy策略,也就是当有第九个人进来的时候,不进行处理,并且抛出异常
我们通过代码来自定义一个线程池,对应的参数跟银行办理业务一致
public static void main(String[] args) {
// 自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
for (int i = 1; i <= 9; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + ":ok");
});
}
// 关闭线程池
threadPool.shutdown();
}
线程池的四种拒绝策略
拒绝策略接口RejectedExecutionHandler有四个实现类,代表四种不同的拒绝策略,默认使用的是 AbortPolicy
- AbortPolicy:当触发拒绝策略时,不进行处理,抛出异常(终止策略,线程池会抛出异常并终止执行,它是默认的拒绝策略)
- CallerRunsPolicy:哪来的去哪里,也就是当触发拒绝策略时,将不再是线程池去处理,一般是main线程去处理(把任务交给当前线程来执行)
- DiscardPolicy:当触发拒绝策略时,丢掉任务,不会抛出异常(忽略此任务,指的是最新的任务)
- DiscardOldestPolicy:当触发拒绝策略时,不会抛出异常,会尝试去和最早的线程竞争一下,如果最早的线程执行完毕,可以拿到这个线程,否则还是丢弃任务(忽略最早的任务,指的是最先加入队列的任务)
如何定义最大线程数
一般从CPU密集型和IO密集型进行考虑
① CPU密集型:指的是电脑的CPU是几核的,就设置最大线程数为几,这样可以保证CPU的效率最高,因为每台电脑的CPU核数都是不一样的,所以设置最大线程数应该通过代码动态设置,不应该写死
Runtime.getRuntime().availableProcessors(); // 获取CPU核数
②IO密集型:设置的最大线程数需要大于程序中非常耗IO的线程数
线程池的五种状态
- RUNNING:能够接收新任务,以及对已添加的任务进行处理
- SHUTDOWN:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务
- STOP:不接收新任务,不处理已添加的任务,并且会中断正在处理的任务
- TIDYING:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态
- TERMINATED:线程池彻底终止,就变成TERMINATED状态
线程池的任务执行流程
ThreadPoolExecutor 的执行方法 excute 和 submit 的区别
execute() 和 submit() 都是用来执行线程池任务的,它们最主要的区别是,submit() 方法可以接收线程池执行的返回值,而 execute() 不能接收返回值。submit() 方法可以配合 Futrue 来接收线程执行的返回值以及取消线程任务等操作。它们的另一个区别是 execute() 方法属于 Executor 接口的方法,而 submit() 方法则是属于 ExecutorService 接口的方法。(ExecutorService 接口继承于Executor 接口)
单例模式创建线程池
public class Test {
private static ThreadPoolExecutor threadPool;
private static final AtomicInteger threadNumberAtomic = new AtomicInteger();
/***
* 单例模式获取线程池
*/
public static ThreadPoolExecutor getThreadPool() {
if (threadPool != null) {
return threadPool;
}
synchronized (Test.class) {
if (threadPool == null) {
threadPool = new ThreadPoolExecutor(2, 2, 10000, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(10),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("thread-" + threadNumberAtomic.getAndIncrement());
return thread;
}
});
}
return threadPool;
}
}
}