目录
1.线程池是什么
2.标准库中的线程池
2.1ThreadPoolExecutor
2.2构造方法参数介绍
2.3拒绝策略(面试易考)
2.4Executor的使用
3.实现线程池
1.线程池是什么
线程池是一种用来管理线程的机制,它可以有效地控制线程的创建、复用和销毁,从而提高程序的性能和资源利用率。
想象这么⼀个场景:
在学校附近新开了⼀家快递店,老板很精明,想到⼀个与众不同的办法来经营。店里没有雇⼈,而是 每次有业务来了,就现场找⼀名同学过来把快递送了,然后解雇同学。这个类⽐我们平时来⼀个任 务,起⼀个线程进行处理的模式。
很快⽼板发现问题来了,每次招聘 + 解雇同学的成本还是非常高的。老板还是很善于变通的,知道 了为什么⼤家都要雇⼈了,所以指定了⼀个指标,公司业务⼈员会扩张到 3 个⼈,但还是随着业务 逐步雇⼈。于是再有业务来了,老板就看,如果现在公司还没 3 个⼈,就雇⼀个⼈去送快递,否则 只是把业务放到⼀个本本上,等着 3 个快递⼈员空闲的时候去处理。这个就是我们要带出的线程池的模式。
线程池最大的好处就是减少每次启动、销毁线程的损耗。
2.标准库中的线程池
2.1ThreadPoolExecutor
2.2构造方法参数介绍
以最后一个构造方法为例:
Java的 ThreadPoolExecutor 是一个线程池执行器,用于管理和调度线程的执行。它有以下几个参数:
1.corePoolSize:核心线程数
即线程池中保持活动状态的最小线程数。如果线程池中的线程数小于corePoolSize,则即使其他线程是空闲的,ThreadPoolExecutor也会创建新的线程来处理任务。
2.maximumPoolSize:最大线程数
即线程池中允许的最大线程数。当队列满了且当前线程数小于maximumPoolSize时,ThreadPoolExecutor会创建新的线程来处理任务。
3.keepAliveTime:线程保持活动的时间
即当线程池中的线程数量大于corePoolSize时,空闲线程被保留的最长时间。超过这个时间,空闲线程将被终止。
4.unit:线程保持活动时间的单位
可以是纳秒、微秒、毫秒、秒、分钟、小时或天。
5.workQueue:任务队列
用于保存等待执行的任务。ThreadPoolExecutor提供了多种类型的队列,如ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue等。
6.threadFactory:线程工厂
用于创建新线程。可以通过实现ThreadFactory接口来自定义线程的创建过程。
7.handler:拒绝策略
用于处理无法添加到线程池的任务。拒绝策略可以ThreadPoolExecutor提供的几种默认策略,如AbortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy,也可以自定义实现RejectedExecutionHandler接口来定义自己的策略。
这些参数可以通过ThreadPoolExecutor的构造方法来设置,也可以通过相应的setter方法进行设置。根据具体的需求,可以调整这些参数来优化线程池的性能和行为。
2.3拒绝策略(面试易考)
在多线程编程中,当线程池无法接受新的任务时,就会触发拒绝策略(RejectedExecutionHandler)。拒绝策略是一个接口,用于定义当线程池无法接受新的任务时应该如何处理这些被拒绝的任务。
在Java中,有四种内置的拒绝策略:
1.AbortPolicy(默认):当线程池无法接受新的任务时,会抛出RejectedExecutionException异常。
2.CallerRunsPolicy:当线程池无法接受新的任务时,会由调用execute方法的线程来执行该任务。
3.DiscardOldestPolicy:当线程池无法接受新的任务时,会抛弃队列中最旧的任务,然后尝试再次提交新的任务。
4.DiscardPolicy:当线程池无法接受新的任务时,会直接抛弃被拒绝的任务。
除了以上四种内置的拒绝策略,我们还可以自定义拒绝策略,只需要实现RejectedExecutionHandler接口,并实现其唯一的方法rejectedExecution(Runnable r, ThreadPoolExecutor executor)。在该方法中,可以根据需求实现自定义的拒绝逻辑,如记录日志、发送通知等。然后,可以通过ThreadPoolExecutor的setRejectedExecutionHandler方法将自定义的拒绝策略设置给线程池。
ThreadPoolExecutor 本身用起来比较复杂, 因此标准库还提供了另一个版本, 把ThreadPoolExecutor封装了一下. 这个版本就是Executors类.
Executors类创建的线程池适用于一些简单的场景,不需要过多的自定义配置。而ThreadPoolExecutor适用于需要更多自定义配置的场景,可以根据需要灵活地配置线程池。
Executors中的方法:
2.4Executor的使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadDemo32 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(4);
//使用submit添加任务
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
});
//ThreadPoolExecutor也是通过submit添加任务, 只是构造方法不同
}
}
3.实现线程池
- 核⼼操作为 submit, 将任务加⼊线程池中
- 使⽤ Worker 类描述⼀个⼯作线程. 使⽤ Runnable 描述⼀个任务.
- 使⽤⼀个 BlockingQueue 组织所有的任务
- 每个 worker 线程要做的事情: 不停的从 BlockingQueue 中取任务并执⾏.
- 指定⼀下线程池中的最⼤线程数 maxWorkerCount; 当当前线程数超过这个最⼤值时, 就不再新增 线程了.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class MyThreadPoolExecutor {
private final List<Thread> threadList = new ArrayList<>();
private BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
//n来指定创多少的线程
public MyThreadPoolExecutor(int n) {
for(int i = 0; i<n; i++) {
Thread t = new Thread(()-> {
//线程要做的事情是把任务队列中的任务不停的取出来,并且去执行
while(true) {
try {
//此处的take带有阻塞功能, 如果队列为空, 此处就阻塞
Runnable runnable = queue.take();
//取出一个任务就执行一个任务即可
runnable.run();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
threadList.add(t);
}
}
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
}
使用示例:
public class ThreadDemo33 {
public static void main(String[] args) throws InterruptedException {
MyThreadPoolExecutor executor = new MyThreadPoolExecutor(4);
for (int i = 0; i < 1000; i++) {
int n = i;
executor.submit(new Runnable() {
@Override
public void run() {
System.out.println("执行任务" + n + ", 当前线程为:" + Thread.currentThread().getName());
}
});
}
}
}