#详细介绍!!!线程池

本篇详细:

1.介绍了什么是线程池

2.使用线程池有什么好处

3.线程池的工作流程

4.线程池的各个参数介绍

5.如何编写Java代码来创建线程池

6.使用线程池的注意事项

目录

一:什么是线程池

二:为什么使用线程池来管理线程

三:线程池的工作流程

四:线程池的各个参数介绍

五:使用Java代码创建线程池

5.1 依靠Executors工厂类来创建

  5.2 依靠ThreadPoolExecutor类来创建

六:使用线程池的注意事项

1.合理的设置线程的数量


#详细介绍!!!线程池_第1张图片


一:什么是线程池

线程池总的来说:就是创建的一个进行线程管理的容器集合,主要为了提高管理线程效率,是一种多线程处理的形式。

线程池:就是开发者根据需求创建多个线程放到一个池中(逻辑意义上),来统一管理这些线程,由池中的任务队列来直接给池中的空闲线程分配任务,大致为这样的一个过程

#详细介绍!!!线程池_第2张图片

二:为什么使用线程池来管理线程

使用线程池的主要目的就是提高线程的调度执行和管理上的效率

优势:

1.降低资源的开销

没有线程池时线程的创建和销毁一般是由操作系统来进行的,而操作系统对线程的控制一般又是不可控的,那么当线程超过空闲时间,而线程就可能会被自动销毁,此时这中反复的操作就有可能出现线程频繁创建和销毁的情况,虽然说线程的创建和销毁的效率对比进程已经优化了很多,但是依旧会造成大量的系统资源上的浪费。

那么如果利用线程池来集中创建线程进行管理,并且核心线程创建完成后,没任务也不会进行销毁。此时就大大减少了系统频繁对线程的创建和销毁造成的资源浪费了。

2.提高任务的响应速度

任务需要线程来执行,而线程由系统进行调度又是无序的,那么就有可能在任务过多时,导致系统响应该任务过慢,此时这种把任务直接让系统来分配线程执行,我们称为用户态+内核态。

当我们(用户)把一个任务交给系统来分配线程(内核)来进行执行时,此时系统不一定会直接响应去执行我们的任务,因为系统需要同时处理大量的响应,导致任务获得响应的速度较低。

如果使用我们自己创建的线程池来进行任务的响应,那么此时就是一个纯用户态的操作了。

因为线程池中有自己的线程,去执行线程池对应的任务队列中的任务,当线程空闲就直接去队列中获取其他任务,直接响应;且由于有核心线程的存在,此时线程池中的核心线程一直都在,并不会被销毁,此时也省去了创建线程进行的时间开销,进一步使得响应速度变快。

3.可管理性

利用线程池集中统一管理,监控池中的线程,避免由于线程数量过多且线程之间相互进行抢占系统资源导致线程大面积堵塞

三:线程池的工作流程

大致如下图:

#详细介绍!!!线程池_第3张图片

1.线程池在创建时,就会指定好各个参数的数量(例如:核心线程,临时线程等)

2.线程池有一个与之对应的任务队列(由于线程池作用的环境一般是多线程并发的状态,此时任务队列也是线程安全的数据结构)

3.我们在使用线程池时只需要往线程池的任务队列中添加任务,此时

        3.1如果池中的线程小于核心线程数就会创建核心线程再执行任务,且执行完不进行销毁

        3.2如果池中的线程大于核心线程数,小于临时线程数,此时就会把任务分配给空闲的线程如果线程全部都被占用此时就会创建临时线程来执行任务

        3.3如果池中线程数已满(核心线程数+临时线程数),此时就会运行线程池的拒绝策略

注意:线程池的拒绝策略后面单独进行讲解(重点面试题)

四:线程池的各个参数介绍

查看官方文档:

查看官方文挡,线程池的构造方法,我们可以看到如下的参数

参数介绍:

1. int corePoolSize :核心线程数

线程池中的核心线程创建之后就不会被销毁,需要用的时候,直接安排任务

2. int maximumPoolSize:最大线程数

最大线程数就是线程池允许同时存在的线程的最大数量:核心线程数+临时线程数

临时线程在执行完任务之后,空闲一定的时间会自动销毁,需要用的时候再创建

3.long keepAliveTime临时线程的空闲存活时间

与第2个对应,临时线程空闲一定时间后会自动销毁,而keepAliveTime就是设置的空闲时间

4.TimeUnit unit:时间单位

时间参数的单位:例如TimeUnit SECOND(秒),设置时间单位为秒

5.BlockingQueue workQueue:堵塞队列(用于记录存储需要执行的任务)

workQueue就是我们前面一直说的任务队列,线程池中都有一个用于记录任务的队列

BlockingQueue 为一个线程安全的阻塞队列,参数为Runnable:为一个实际任务,通过重写重写run方法来描述具体的任务逻辑

6.ThreadFactory threadFactory:工厂类:用于辅助线程创建的类(参考工厂模式)

7.RejectedExecutionHandler handler:线程池的拒绝策略

指定线程池的拒绝策略,如果线程池满了,依旧在添加任务,那么就可根据设置的拒绝策略来进行拒绝(专门讲线程池拒绝策略的时候再详细介绍)

五:使用Java代码创建线程池

5.1 依靠Executors工厂类来创建

1. newFixedThreadPool(int n),返回类型为ExecutorService接口类型

指定线程池中线程的数量为n,并且这些线程都为核心线程

ExecutorService pool = Executors.newFixedThreadPool(10);

2.newSingleThreadPool(),返回类型为ExecutorService接口类型

单例线程池,固定线程池中的线程数量为1,且为核心线程

ExecutorService pool = Executors.newSingleThreadExecutor();

3.newCatchThreadPool(),返回类型为ExecutorService接口类型

创建的线程池中核心线程数为0,最大线程数为Integer.MAX_VALUE

keepAliveTime:60 ; TimeUnit为SECOND(秒):临时线程空闲等待时间为60秒,时间到了自动销毁

总结:以这种方式创建的线程池,池中的线程都是临时线程

ExecutorService pool = Executors.newCachedThreadPool();

4.newScheduledThreadPool(int n),返回类型为ScheduledExecutorSerives接口类型

定时任务线程池:其中可设置池中线程定时或者周期性的执行池中的任务

n为核心线程数

介绍几种该线程池的安排任务的方法区别

4.1 调用schedule方法个线程池安排任务(普通定时任务)

4.2 调用scheduleAtFixedRate方法给线程池安排周期性任务(从任务开始时计算周期定时时间)

4.3调用scheduleWithFixedDelay方法给线程池安排周期性任务(从任务结束开始计算周期定时时间)

//1.普通定时任务
pool.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
          System.out.println("hello1");
      }
},2,TimeUnit.SECONDS);

//2.周期任务
pool.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
          System.out.println("hello1");
      }
},2,5,TimeUnit.SECONDS);

//3.周期任务
pool.scheduleWithFixedDelay(new Runnable() {
     @Override
     public void run() {
         System.out.println("hello2");
     }
},2,5,TimeUnit.SECONDS);

上诉三个代码的作用:

1. 安排了一个打印hello1的任务,定时2秒后执行任务

2.安排了一个周期性打印hello2的任务,定时2秒后执行,并且从任务开始执行时开始计算,5秒后再次执行该任务,以这种周期执行

3.安排了一个周期性打印hello3的任务,定时2秒后执行,并且从任务结束是开始计算周期间隔时间,间隔时间为5秒

5.newSingleThreadScheduleExecutor();返回类型为ScheduledExecutorSerives接口类型

单个线程的定时任务或者周期任务的线程池。池中只有一个线程,其他与4基本一致

ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();

  5.2 依靠ThreadPoolExecutor类来创建

通过ThreadPoolExecutor类来更加灵活的创建线程池,根据ThreadPoolExecutor类的构造方法传递一系列参数进行灵活创建。

代码:

BlockingQueue queue = new LinkedBlockingQueue<>();
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,queue);

指定一个线程安全的阻塞队列来存储线程池中的任务,

new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,queue):

表示corePoolSize(核心线程数)为5;maximumPoolSize(最大线程数)为10

keepAliveTime(临时线程最大空闲时间)为60;TimeUnit.SECOND:单位为秒

queue为任务队列

当然还可以根据需求增加其他参数,参数详情参照前面线程池参数介绍

六:使用线程池的注意事项

1.合理的设置线程的数量

线程池的工作线程数设置应根据实际情况配置,CPU密集型业务(搜索、排序等)CPU空闲时间较少,线程数就不需要设置太多。N核服务器,通过执行业务的单线程分析出本地计算时间为x,等待时间为y,则工作线程数(线程池线程数)设置为 N*(x+y)/x,能让CPU的利用率最大化

你可能感兴趣的:(JavaEE初级,jvm,面试,java,java-ee,开发语言)