目录
[1 线程池是什么](#1 线程池是什么)
[2 Java标准库中的线程池](#2 Java标准库中的线程池)
[3 自己实现一个固定线程数的线程池](#3 自己实现一个固定线程数的线程池)
1 线程池是什么
在多线程编程中,每当需要一个线程的时候,我们就要进行创建线程的操作。但是这样频繁创建销毁线程的操作是很消耗资源的。所以,我们有了"线程池"这个东西,线程池就是先把线程创建好放在"池子"里,等到我们需要使用线程的时候直接从里面拿就好了,并且在用完之后,将线程再重新放入这个"池子"中。
所以,线程池最大的好处就是减少了每次创建和销毁线程的资源损耗。
2 Java标准库中的线程池
Java标准库中的线程池是ThreadPoolExcutor。
线程池的工作原理就是,先创建一个线程池,然后通过submit方法(submit方法中的是一个Runnable,这里使用的是匿名类的方法来创建对象)给任务队列提交一个任务,这里的任务队列其实就是阻塞队列,把这个任务提交之后,通过阻塞队列安排线程进行执行这个任务。
ThreadPoolExcutor有很多构造方法,这里我们选择参数最多的一种来进行介绍:
java
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程,就是一个线程池中至少要包含的线程个数。线程池创建的时候这么多数量的线程就会随之创建,等到线程池销毁的时候,这些线程才全部销毁。
- maximumPoolSize:最大线程数,一个线程池中包含了核心线程和非核心线程,其中非核心线程是自适应的,也就是说如果提交给线程池的任务多,那么这些线程就存在,如果提交给线程池任务少,那么这些线程就销毁。
- keepAliveTime:是对于非核心线程在没有处理任务的时候能够存活的时长。
- unit:这是一个枚举类型,其中的内容是线程存活时长的单位。
- workQueue:阻塞队列,也就是前面我们提到的任务队列。
- threadFactory:工厂模式,与单例模式相同也是一种设计模式,但是工厂模式是统一的构造并初始化线程的。
- handler:拒绝策略,在线程池中,如果任务队列满了,事实上不会真的进行阻塞,而是执行拒绝策略的相关代码,拒绝策略又有好多种:
- AbortPolicy:超出负荷,直接抛出异常
- CallerRunPolicy:让调用submit方法的线程执行本次任务
- DiscardOldestPolicy:丢弃队列中最老的任务,来执行本次任务
- DiscardPolicy:直接丢弃本次任务
为了简化线程池的使用,使用Excutors来对ThreadPoolExcutor进行封装。
Excutors创建线程池有四种方式:
- newFixedThreadPool:创建固定线程数的线程池(核心线程数和最大线程数相等)
- newCachedThreadPool:创建线程数目动态增长的线程池(最大线程数是一个很大的数,能够一直增长)
- newSingleThreadExecutor:创建只包含单个线程的线程池
- newScheduledThreadPool:设定延迟时间后执行命令,或者定期执行命令
示例:
java
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(()-> {
@Override
public void run() {
System.out.println("你好");
}
});
}
但是此处存在一个问题就是,在线程把任务都执行结束之后,程序并不会结束。
示例:
java
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
int n = i;
pool.submit(()->{
System.out.println(n);
});
}
// pool.shutdown();
// pool.awaitTermination(3, TimeUnit.SECONDS);
}
// 当任务结束之后,程序并不会结束,手动结束之后,控制台返回:
// Process finished with exit code 130
// 这说明程序不是自动结束的
// 程序不会结束的原因是,虽然任务已经处理结束,但是阻塞队列中的take还在阻塞等待新的任务(线程池中的线程是前台线程,会阻碍程序结束)
// 所以这个时候需要shutdown方法来关闭线程池 但是这个方法不能保证需要执行的任务全部执行完毕
// 如果要等任务执行完毕之后再进行关闭,那么就使用awaitTermination方法
// awaitTermination方法的返回值为boolean类型,第一个参数表示等待时长,第二个参数表示等待时长的单位
3 自己实现一个固定线程数的线程池
java
/**
* 实现一个固定线程数的线程池
* 在这个代码中,其实只需要实现一个 submit 方法,和 创建一个固定数量线程的方法(创建固定数量的线程在构造方法中进行)
*/
class MyThreadPool{
// 因为要提交的任务是 Runnable 所以这里<>中是Runnable
private BlockingQueue<Runnable> queue = null;
// n是这个线程池的线程个数
// 在构造方法中创建线程,保证在创建线程池的时候就创建线程了
public MyThreadPool(int n){
queue = new ArrayBlockingQueue<>(1000);
// 创建 n 个线程
for (int i = 0; i < n; i++) {
Thread t = new Thread(()->{
try {
// 创建线程
while(true) {
// submit 之后先把任务放到 queue 中
// 所以要从 queue 中取出任务
Runnable task = queue.take();
task.run();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
// 创建好线程之后,启动线程
t.start();
}
}
// 这里的 submit 方法就起到一个把任务放到阻塞队列中的一个效果
public void submit(Runnable task) throws InterruptedException {
queue.put(task);
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(5);
for (int i = 0; i < 10; i++) {
pool.submit(()->{
System.out.println("你好");
});
}
}
}