线程池的执行流程

一、概述:

线程池就是能够较好管理线程的池子。频繁的创建线程很消耗系统资源,而线程池它能够避免线程的频繁创建和销毁。在线程池中的线程执行完一个线程任务后,当前线程不会立即销毁,它会在线程池中存活一段时间,若在这段时间,线程池中提交了新的任务,就可以直接拿去线程池中的线程,实现了线程的复用。下面来看看线程池的执行流程,看看它是如何实现线程复用的。

二、线程池的执行流程:

线程池的执行流程_第1张图片

线程池的执行是从向线程池中提交线程任务开始的,线程池接收线程任务:

判断当前线程池中是否有空闲线程:有,则分配空闲线程执行该任务;没有,判断当前线程的核心线程数是否超出:未超出,创建新的核心线程执行该任务;超出,会先判断工作队列是否已满:没满,将该任务放入工作队列进行等待线程池中的核心线程空闲下来在执行;满了,判断当前线程数是否已达到设定的最大线程数:不大于,创建非核心线程来处理该任务;大于,则采用拒绝策略。

三、线程池的创建及参数

1、通过Executor工具类的静态方法

在前面我们有通过线程池的方式来创建线程,借助的是Executor工具类中的各种静态方法

 newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool

来获取不同类型的线程池,但是由于该方式创建出的线程池很多参数都被写死,不建议这样创建线程池。在调用Executor工具类中的静态方法中,其实内部是通过ThreadPoolExecutor()构造方法中传入参数的方式创建线程池。

2、通过ThreadPoolExecutor构造函数【推荐】

                                继承                                                 实现

ThreadPoolExecutor------------>AbstractExecutorService------------->ExecutorService

通过继承和实现关系可知:ThreadPoolExecutor是ExecutorService接口的实现类

ExecutorService executorService=new ThreadPoolExecutor(核心线程数,最大线程数,存活时间,时间的单位,阻塞队列)

ThreadPoolExecutor()中有不少参数,我们只有了解了每个参数的含义,才能在不同情况下合理运用线程池,线程池创建的参数配置:

参数 含义
corePoolSize 线程池核心线程数
maximumPoolSize 线程池最大线程数
keepAliveTime 非核心线程线程存活时间
TimeUnit 时间单位
BlockingQueue 阻塞工作队列(存储等待执行的任务)
ThreadFactory 线程工厂(真正创建线程的类)
RejectedExecutionHandler 拒绝策略

其中RejectedExecutionHandler参数有四种,都是以内部类的形式在ThreadPoolExecutor类中

  • AbortPolicy:丢弃任务并抛出RejectedExecutionException【默认】
  • DiscardPolicy:丢弃任务但不抛异常
  • DiscardOldestPolicy:丢弃工作队列中来的最早的任务,然后翻入工作队列
  • CallerRunsPolicy:交给调用的线程处理该任务

下面我们看一个例子:

创建了一个线程池(5个核心线程,最大线程池数为7,存活时间为1s,无界阻塞队列)

public class Demo03 {
    public static void main(String[] args) {
        ExecutorService executorService=
                new ThreadPoolExecutor(5,7,1, TimeUnit.SECONDS,new LinkedBlockingDeque<>());
        for(int i=1;i<=8;i++){
            executorService.execute(new Task("任务"+i));
        }
        executorService.shutdown();
    }
}

执行结果:

线程池的执行流程_第2张图片

 我们发现通过线程池的方式创建出的线程名称是固定格式,因为当我们没有传入ThreadFactory参数,内部默认采用DefaultThreadFactory,它的内部对线程的命名做了如上图运行结果的格式规定

由于线程的真正创建是在我们所传入的ThreadFactory类型参数,ThreadFactory是一个接口,想要自定义线程的名称需要我们自定义ThreadFactory的实现类接口,下面我们通过自定义的线程工厂来对线程池的线程进行命名

class MyThreadFactory implements ThreadFactory{

    private final AtomicInteger n=new AtomicInteger(0);
    @Override
    public Thread newThread(Runnable r) {
        Thread t=new Thread(r);
        t.setName("线程池-订单线程"+n.incrementAndGet());
        return t;
    }
}

然后我们修改创建线程池的代码【加入ThreadFactory参数】

ExecutorService executorService=
                new ThreadPoolExecutor(5,7,1, TimeUnit.SECONDS,new LinkedBlockingDeque<>(),new MyThreadFactory());

线程池的执行流程_第3张图片

 通过在我们自定义的MyThreadFactory类重写的thread()中通过setName设置线程名称

四、线程池分类

根据Java中封装好的Executors类中的线程池分类:FixedThreadPool(线程数固定的线程池)、CachedThreadPool(线程数根据任务动态调整的线程池)、SingleThreadExecutor(仅提供一个单线程的线程池)、ScheduledThreadPool(能实现定时、周期性任务的线程池)四种,下面通过一个案例来了解四种不同线程池的执行机制

Runnable实现类

 class Task implements Runnable{
     //线程任务名
     private String taskName;
 public Task(String taskName){
     this.taskName=taskName;
 }
 ​
 @Override
 public void run() {
     System.out.println("当前时间:"+ LocalDateTime.now()); System.out.println(Thread.currentThread().getName()+":"+taskName+"开始执行");
     try {
         Thread.sleep(1000);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
     System.out.println(Thread.currentThread().getName()+":"+taskName+"执行结束了");
     }
 }

1、创建固定数量【4】的线程池,向线程池中提交6个线程任务

 public class Demo01 {
     public static void main(String[] args) throws InterruptedException {
         //创建线程池 核心线程数=最大线程数【FixedThreadPool】
         ExecutorService executorService= Executors.newFixedThreadPool(4);
         for(int i=1;i<=6;i++){
             //提交线程任务
             executorService.execute(new Task("线程任务"+i));
         }
         executorService.shutdown();
         while(!executorService.awaitTermination(1,TimeUnit.SECONDS)){
             System.out.println("线程池还未关闭");
         }
         System.out.println("线程池已关闭");
     }
 }

线程池中共4个线程,有2个线程得到了重用

FixedThreadPool:线程数固定的线程池

核心线程数=最大线程数,存活时间为0,采用LinkedBlockingQueue无界队列,默认拒绝策略为AbortPolicy

  1. 提交线程任务

  2. 线程数

  3. 线程数=corePoolSize(maximumPoolSize),将当任务加入阻塞队列,直到有线程空闲

2、创建线程数根据任务动态调整的线程池

当Demo01中第4行代码换为:

ExecutorService executorService=Executors.newCachedThreadPool();

线程池的执行流程_第4张图片

CachedThreadPool为并发执行的6个任务都分别创建了一个线程

CachedThreadPool:线程数根据任务动态调整的线程池

核心线程数=0,最大线程数默认Integer.MAX_VALUE,存活时间为60s,采用SynchronousQueue同步队列

  1. 提交线程任务

  2. 任务放入同步队列

  3. 有空闲线程,从同步队列取出并执行

  4. 没有空闲线程,新建线程执行

  5. 60s内未接收到任务,该线程会被关闭

3、创建仅提供一个单线程的线程池

当Demo01中第4行代码换为:

 ExecutorService executorService= Executors.newSingleThreadExecutor();

线程池的执行流程_第5张图片

线程池中只有1个线程,每当1个任务执行接收后,才处理下一个任务

SingleThreadExecutor:仅提供一个单线程的线程池

核心线程数=最大线程数=1

存活时间为0,采用LinkedBlockingQueue队列

单线程的方式适合执行任务串行的场景

4、实现定时、周期性任务的线程池

当Demo01中第4行代码换为:

 ExecutorService executorService=Executors.newScheduledThreadPool(2);

线程池的执行流程_第6张图片

线程池中的2个线程会每间隔1s执行任务

ScheduledThreadPool:能实现定时、周期性任务的线程池

核心线程数通过传参指定,最大线程数为Integer.MAX_VALUE,采用DelayedWorkQueue,存活时间为0

用于周期性执行任务的线程池

五、线程池的优点:

  1. 线程池中的线程可以重复利用,避免了线程的频繁创建与摧毁带来的性能损耗
  2. 线程池中的任务,只要池中有空闲线程就能立即执行当前任务,不需要手动调用start()来启动线程
  3. 线程池的概念能够很好的规范线程的数量和对线程的管理

六、线程池的状态

线程池的状态:Running、Shutdown、Stop、Tidying、Terminated五种

Running(运行):当线程池创建出来时,它就处于运行状态了,但目前线程中的线程池任务为0

Shutdown(关闭):线程池调用了shutdown()方法,线程池就会切换到关闭状态

当线程池处于Shutdown状态时,线程池不再接受新的任务,但会把工作队列中的任务处理完

线程池调用了shutdownnow()方法,线程池会进入该Stop状态,此时线程池同样也不会在接受新的任务,并且当前的执行线程也会终止

线程池的执行流程_第7张图片

1、当线程池创建出来就处于Running状态,只不过此时线程池中的线程数为0

2、当线程池中的线程在处理线程任务时没有别的状态,在调用线程池的shutdown()时,线程池会进入shutdown状态 ,线程池不接收新任务,会将工作队列中的任务执行完,然后进入tidying(整理状态)

3、当线程池调用shutdownnow()时,线程池会进入stop(停止状态).当前线程池不会再接收新任务,同时也会中断正在执行的任务

4、处于2的线程池处理完当前线程和工作队列中的线程,也就是线程池中的任务数为0,线程池会进入tidying(整理)状态

同样处于3的线程池中任务数为0,也会进入tidying(整理)状态

5、当进入tidying(整理状态)的线程池的terminated()执行完毕后进入TERMINATED(终止状态)

你可能感兴趣的:(java,开发语言)