Bootstrap

对多线程中线程池的理解

一.概念理解

何为线程池?

线程池的释义正如它的命名:专门用来存放线程的池子(集合类),也就是将线程存储于集合类,使用时从线程池中直接获取,使用结束后将线程放回集合类即可,这样就避免了线程频繁的创建和销毁。

为什么要使用线程池?

我们使用多线程的目的在于利用线程间的阻塞空隙,来提高效率。但是我们使多线程时不免创建多个线程,线程的创建虽然相比于进程的创建所用的开销要小很多,但是线程频繁的开启和销毁仍然会降低处理任务的效率,所以线程池应运而生。我们都知道,创建系统层面的线程需要JVM调用系统层面的API,通过系统创建PCB实现线程的创建,这是在系统中的内核态中完成的,而我们JAVA中实现的功能是在JAVA 层面的用户态中实现的,为了提高效率,我们尽量将工作安排在用户态中,而线程池也是基于这种思想下创建的。

好处:

  1. 降低资源消耗:减少线程的创建和销毁带来的性能开销。

  1. 提高响应速度:当任务来时可以直接使用,不用等待线程创建

  1. 可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。

二.如何使用线程池?

   //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();
        }
    }
}

四.创建系统自带的线程池

我们通过一个实际生活中的例子来对这些概念进行解释:

那么线程池的工作流程是怎样的呢?

关于四种不同的拒绝策略:

创建方式如下:

;