一.概念理解
何为线程池?
线程池的释义正如它的命名:专门用来存放线程的池子(集合类),也就是将线程存储于集合类,使用时从线程池中直接获取,使用结束后将线程放回集合类即可,这样就避免了线程频繁的创建和销毁。
为什么要使用线程池?
我们使用多线程的目的在于利用线程间的阻塞空隙,来提高效率。但是我们使多线程时不免创建多个线程,线程的创建虽然相比于进程的创建所用的开销要小很多,但是线程频繁的开启和销毁仍然会降低处理任务的效率,所以线程池应运而生。我们都知道,创建系统层面的线程需要JVM调用系统层面的API,通过系统创建PCB实现线程的创建,这是在系统中的内核态中完成的,而我们JAVA中实现的功能是在JAVA 层面的用户态中实现的,为了提高效率,我们尽量将工作安排在用户态中,而线程池也是基于这种思想下创建的。
好处:
降低资源消耗:减少线程的创建和销毁带来的性能开销。
提高响应速度:当任务来时可以直接使用,不用等待线程创建
可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。
二.如何使用线程池?
//1.用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,线程空闲60s则会被回收
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//2.创建一个无界队列且固定大小的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//3.创建一个无界队列且只有一个工作线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//4.创建一个单线程执行器,可以在给定时间后执行或者定期执行
ScheduledExecutorService singleScheduleExecutor = Executors.newSingleThreadScheduledExecutor();
//5.创建一个指定大小的线程池,可以在给定时间后执行或者是定期执行
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//6.创建一个指定大小的线程池(不指定大小则为当前机器的cpu核心数),并行执行任务,不保证执行顺序
ExecutorService workStealingPool = Executors.newWorkStealingPool();
JVM提供了6种默认的线程池,分别对应不同的应用场景,但是事实上我们很少用JVM提供的线程池,而是根据我们自己的业务场景创建自定义的线程池。为什么呢?一是因为系统提供的线程池难以满足我们的业务场景,二是当线程创建临时线程时,直接创建最大线程容量,因此在这种开辟策略下 系统提供的默认线程池开销极大。
而JVM提供的线程池体现了通过提供不同的方法来获取不同的线程池,这便是工厂模式的体现,在这里关于工厂模式我们不做过多介绍,大家参考以下博客:https://blog.csdn.net/weixin_43757333/article/details/126294625
三.我们手动实现一个线程池
我们先说一下线程池的实现思路:①自定义实现MyThreadPool类,其中通过阻塞队列实现对任务的存储②通过submit()方法将任务提交至阻塞队列中③通过类中的构造方法将任务通过不同的线程取出并执行
代码如下:
public class MyThreadPool {
BlockingQueue<Runnable>runnables=new LinkedBlockingQueue<>();//阻塞队列
//创建提交任务的方法
public void submit(Runnable runnable) throws InterruptedException {
runnables.put(runnable);
}
//创建取出任务的方法(构造方法)
public MyThreadPool(int num){
//检查num的合法性
if(num<=0){
throw new RuntimeException("输入的数字不合法");
}
//循环进行不断创建线程
for(int i=0;i<num;++i){
Thread thread =new Thread(()->{
while (true){
try {
Runnable take = runnables.take();
take.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
}
}
}
四.创建系统自带的线程池
我们通过一个实际生活中的例子来对这些概念进行解释:
那么线程池的工作流程是怎样的呢?
关于四种不同的拒绝策略:
创建方式如下: