java线程池(详解)

Posted 祁_z

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java线程池(详解)相关的知识,希望对你有一定的参考价值。

线程池介绍

线程池(thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,对线程统一管理

线程池就是存放线程的池子,池子里存放了很多可以复用的线程。

创建线程和销毁线程的花销是比较大的(手动new Thread 类),创建和消耗线程的时间有可能比处理业务的时间还要长。这样频繁的创建线程和销毁线程是比较消耗资源的。(我们可以把创建和销毁的线程的过程去掉)

使用线程池的优势

  1. 提高效率,创建好一定数量的线程放在池中,等需要使用的时候就从池中拿一个,这要比需要的时候创建一个线程对象要快的多。
  2. 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  3. 提升系统响应速度假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;

四种创建线程池的方式

Executors类(并发包)提供了4种创建线程池方法,这些方法最终都是通过配置ThreadPoolExecutor的不同参数,来达到不同的线程管理效果。

newCacheTreadPool

创建一个可以缓存的线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程,没回收的话就新建线程

newFixedThread

创建一个定长的线程池,可控制最大并发数,超出的线程进行队列等待。

newScheduleThreadPool

可以创建定长的、支持定时任务,周期任务执行。

newSingleExecutor

创建一个单线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

newCacheTreadPool

创建一个可以缓存的线程池如果线程池长度超过处理需要,可以灵活回收空闲线程,没回收的话就新建线程。

总结: 线程池的最大核心线程为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程;如果第一个线程任务还没有完成则会新建一个线程

public static void main(String[] args)  
    // 创建可缓存线程池
    ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

    for (int i = 0; i < 5; i++) 
        //创建任务
        Runnable runnable = new Runnable()
            @Override
            public void run() 
                try 
                    Thread.sleep(10);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println(Thread.currentThread().getName());
            
        ;
        newCachedThreadPool.execute(runnable);
    

newFixedThreadPool

创建一个定长的线程池,可控制最大并发数,超出的线程进行队列等待

总结:创建指定长度的线程池,任务超出当前线程池执行线程数量则会一直等待,直到运行。

public static void main(String[] args)  
    // 创建定长线程池
    ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);

    for (int i = 0; i < 5; i++) 
        //创建任务
        Runnable runnable = new Runnable()
            @Override
            public void run() 
                try 
                    Thread.sleep(10);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println(Thread.currentThread().getName());
            
        ;
        // 将任务交给线程池管理
        newFixedThreadPool.execute(runnable);
    

newScheduledThreadPool

创建定长的、支持定时任务,周期任务执行

总结:以下案例中延迟2秒后开始执行线程池中的所有任务。

public static void main(String[] args)  
    // 创建支持定时线程池
    ScheduledExecutorService  newScheduledThreadPool = Executors.newScheduledThreadPool(2);

    for (int i = 0; i < 5; i++) 
        //创建任务
        Runnable runnable = new Runnable()
            @Override
            public void run() 
                System.out.println(Thread.currentThread().getName());
            
        ;
        // 将任务交给线程池管理,延迟2秒后才开始执行线程池中的所有任务
        newScheduledThreadPool.schedule(runnable, 2, TimeUnit.SECONDS);
    

newSingleExecutor

创建一个单线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

public static void main(String[] args)  
    // 创建单线程-线程池,任务依次执行
    ExecutorService   newScheduledThreadPool = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 5; i++) 
        //创建任务
        Runnable runnable = new Runnable()
            @Override
            public void run() 
                System.out.println(Thread.currentThread().getName());
            
        ;
        // 将任务交给线程池管理
        newScheduledThreadPool.execute(runnable);
    

 推荐多线程用法

 推荐通过 new ThreadPoolExecutor() 的写法创建线程池,这样写线程数量更灵活开发中多数用这个类创建线程。

ThreadPoolExecutor核心参数:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

参数

含义

解释

corePoolSize

线程池中的核心线程数

核心线程生命周期无限,即使空闲也不会死亡。

maximumPoolSize

线程池中最大线程数

任务队列满了以后当有新任务进来则会增加一个线程来处理新任务,

(线程总数 < maximumPoolSize)

keepAliveTime

闲置超时时间

当线程数大于核心线程数时,经过keepAliveTime时间将会回收非核心线程

unit

超时时间的单位

(时/分/秒等)

*

workQueue

线程池中的任务队列

存放任务(Runnable)的容器

threadFactory

为线程池提供创建新线程的线程工厂

*

rejectedExecutionHandler

拒绝策略

新增一个任务到线程池,如果线程池任务队列超过最大值之后,并且已经开启到最大线程数时,默认为抛出ERROR异常

线程池的工作原理

多线程四种拒绝策略

四种拒绝策略在ThreadPoolExecutor是四个内部类

AbortPolicy abortPolicy = new ThreadPoolExecutor.AbortPolicy();
DiscardPolicy discardPolicy = new ThreadPoolExecutor.DiscardPolicy();
DiscardOldestPolicy discardOldestPolicy =
        new ThreadPoolExecutor.DiscardOldestPolicy();
CallerRunsPolicy callerRunsPolicy = new ThreadPoolExecutor.CallerRunsPolicy();

1. AbortPolicy

当任务添加到线程池中被拒绝时,直接丢弃任务,并抛出

RejectedExecutionException异常

2. DiscardPolicy

当任务添加到线程池中被拒绝时,丢弃被拒绝的任务,不抛异常

3. DiscardOldestPolicy

当任务添加到线程池中被拒绝时,丢弃任务队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中

 4. CallerRunsPolicy

被拒绝任务的处理程序,直接在execute方法的调用线程中运行被拒绝的任务。

总结:就是被拒绝的任务,直接在主线程中运行,不再进入线程池。

 CallerRunsPolicy这种方式好多同学可能不太理解,这里给大家看个Demo:

public static void main(String[] args) 
    // 创建单线程-线程池,任务依次执行
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 2,
            60, TimeUnit.SECONDS,
            new LinkedBlockingDeque<>(2),
            new ThreadPoolExecutor.CallerRunsPolicy());

    for (int i = 0; i < 10; i++) 
        //创建任务
        Runnable runnable = new Runnable() 
            @Override
            public void run() 
                try 
                    Thread.sleep(20);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println(Thread.currentThread().getName());
            
        ;
        // 将任务交给线程池管理
        threadPoolExecutor.execute(runnable);
    

控制台打印:

如何合理配置线程池

使用线程池时通常我们可以将执行的任务分为两类:

  • cpu 密集型任务
  • io 密集型任务

cpu 密集型任务,需要线程长时间进行的复杂的运算,这种类型的任务需要少创建线程(以CPU核数+1为准),过多的线程将会频繁引起上文切换,降低任务处理速度。

而 io 密集型任务,由于线程并不是一直在运行,可能大部分时间在等待 IO 读取/写入数据,增加线程数量可以提高并发度,尽可能多处理任务。

最后给大家补充一个知识点,配置线程池最好的方式是可以动态修改线程池配置,

例如调用线程池的threadPoolExecutor.setCorePoolSize();方法,

搭配分布式配置中心可以随着运行场景动态的修改核心线程数等功能。

🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈 🌈

🍎原创不易,感觉对自己有用的话就❤️点赞👉收藏💬评论把。

Java多线程:线程池详解

目录

1. 什么是线程池

2. 为什么要使用线程池

3. 线程池创建

3.1 固定数量的线程池(Executors.newFixedThreadPool) 

3.1.1 创建固定数量的线程池

3.1.2 线程池返回结果

3.1.3 submit() VS execut()

3.2.4 线程工厂

3.2 带缓存的线程池(Executors.newCachedThreadPool)

3.3 执行定时任务(Executors.newSingleThreadExecutor)

3.3.1 延迟执行(1次)

3.3.2 固定频率执行( scheduleAtFixedRate)

3.3.3 scheduleAtFixedRate VS scheduleWithFixedDelay

3.4 定时任务单线程(Executors.newSingleThreadScheduledExecutor)

3.5 单线程线程池(Executors.newSingleThreadExecutor)

3.6 根据当前CPU生成线程池(Executors.newWorkStealingPool)

3.7 手动方式(ThreadPoolExecutor) 

3.7.1 创建忽略最新任务的线程池

 3.7.2 ThreadPoolExecutor 参数说明

3.7.3 线程池执行流程

3.7.4 拒绝策略(5种(4(JDK提供的) + 1(自定义拒绝策略)))

4. 线程池状态

5. 究竟选用哪种线程池 


1. 什么是线程池

        线程池(ThreadPool)是⼀种基于池化思想管理和使用线程的机制。它是将多个线程预先存储在⼀个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。

2. 为什么要使用线程池

原因有以下几条:

  1. 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。  
  2. 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统⼀的分配、调优和监控。
  4. 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池 ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

        阿里巴巴在其《Java开发手册》中也强制规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

        线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

3. 线程池创建

线程池的创建方法总共有 7 种,总体来说可分为 2 类:

  1. 通过 ThreadPoolExecutor 创建的线程池;
  2. 通过 Executors 创建的线程池。

线程池的创建方式总共包含以下 7 种(其中前 6 种是通过 Executors 创建的,最后 1 种是通过 ThreadPoolExecutor 创建的):

  1. Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
  2. Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后回收,若线程数不够,则新建线程;
  3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序;
  4. Executors.newScheduledThreadPool:创建⼀个可以执行延迟任务的线程池;
  5. Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池;
  6. Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定),根据当前CPU⽣成线程池【JDK1.8 添加】;
  7. ThreadPoolExecutor:手动创建线程池的方式,它包含了 7 个参数可供设置。

3.1 固定数量的线程池(Executors.newFixedThreadPool) 

3.1.1 创建固定数量的线程池

示例代码

public class ThreadPoolDemo1 
    public static void main(String[] args) 
        // 1.创建一个包含5个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        // 2.使用线程池执行任务
        for (int i = 0; i < 5; i++) 
            // 给线程池添加任务
            threadPool.submit(() -> System.out.println("线程名称:" + Thread.currentThread().getName()));
        

        // 2.使用线程池执行任务2
        for (int i = 0; i < 10; i++) 
            // 给线程池添加任务
            threadPool.execute(() -> System.out.println("线程名称:" + Thread.currentThread().getName()));
        
    

 运行结果

3.1.2 线程池返回结果

示例代码

public class ThreadPoolDemo2 
    public static void main(String[] args) throws ExecutionException, InterruptedException 
        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        Future<Integer> future = threadPool.submit(() -> 
            int num = new Random().nextInt(100);
            System.out.println("生成随机数:" + num);
            return num;
        );
        System.out.println("得到线程池返回结果:" + future.get());
    

 运行结果

 成功获得了线程池返回结果。

3.1.3 submit() VS execut()

        使用线程池执行任务有两种方式:submit() 和 execut() 。这两种方式的区别如下:

        可以看到,使用 submit() 可以执行带有返回值的任务或者无返回值的任务,而 execut() 只能执行不带返回值的任务。

3.2.4 线程工厂

作用:为线程池提供现成的创建。

提供的功能

  1. 设置线程池中线程的命名规则;
  2. 设置线程优先级;
  3. 设置线程分组;
  4. 设置线程类型(守护线程 || 用户线程)。

示例代码

public class ThreadPoolDemo3 
    public static void main(String[] args) 
        // 1.创建线程工厂
        ThreadFactory factory = r -> 
            // 一定要把任务 Runnable 设置给新线程
            Thread thread = new Thread(r);
            // 设置线程的命名规则
            thread.setName("我的线程:" + r.hashCode());
            // 设置线程的优先级
            thread.setPriority(Thread.MAX_PRIORITY);
            return thread;
        ;
        ExecutorService service = Executors.newFixedThreadPool(5, factory);
        for (int i = 0; i < 5; i++) 
            service.submit(() -> 
                // 任务
                Thread thread = Thread.currentThread();
                System.out.println("线程池开始执行:" + thread.getName() + "线程池优先级:" + thread.getPriority());
            );
        
    

 运行结果

3.2 带缓存的线程池(Executors.newCachedThreadPool

        线程池会根据任务数创建线程,并且在一定时间内可以重复使用这些线程。

示例代码

public class ThreadPoolDemo4 
    public static void main(String[] args) 
        // 创建线程池
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 1000; i++) 
            int finalI = i;
            service.submit(() -> System.out.println("i:" + finalI + " 线程名称:" + Thread.currentThread().getName()));
        
    

 运行结果

 该方式适用于短时间有且有大量任务的场景,它的缺点是可能占用很多资源。

3.3 执行定时任务(Executors.newSingleThreadExecutor

3.3.1 延迟执行(1次)

示例代码

public class ThreadPoolDemo12 
    public static void main(String[] args) 
        ScheduledExecutorService threadPool =
                Executors.newScheduledThreadPool(10);
        // 定时任务
        System.out.println("设置定时任务:" + new Date());
        // 延迟 n 秒后执⾏(只执⾏⼀次)
        threadPool.schedule(() -> System.out.println("schedule:" + new Date()), 2, TimeUnit.SECONDS);
    

执行结果

 延迟 2s 后执行一次。

3.3.2 固定频率执行( scheduleAtFixedRate

示例代码

public class ThreadPoolDemo13 
    public static void main(String[] args) 
        ScheduledExecutorService threadPool =
                Executors.newScheduledThreadPool(10);
        // 定时任务
        System.out.println("设置定时任务:" + new Date());
        threadPool.scheduleAtFixedRate(() -> 
                System.out.println("scheduleAtFixedRate:" + new Date()), 3, 2, TimeUnit.SECONDS);
    

运行结果

延迟3s后执行,之后每2s执行一次。

参数解释

  1. 参数1:执行任务;
  2. 参数2:延迟 n 秒后执行;
  3. 参数3:执行定时任务的频率;
  4. 参数4:配合参数3使用的时间单位。

3.3.3 scheduleAtFixedRate VS scheduleWithFixedDelay

         scheduleAtFixedRate 是以上⼀次任务的开始时间,作为下次定时任务的参考时间的(参考时间+延迟任务=任务执行)。

示例代码

public class ThreadPoolDemo5 
    public static void main(String[] args) 
        // 创建线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务执行时间:" + LocalDateTime.now());

        // 2s之后开始执行定时任务,定时任务每隔4s执行一次
        service.scheduleAtFixedRate(() -> 
            System.out.println("执行了任务:" + LocalDateTime.now());
            try 
                Thread.sleep(5 * 1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        , 2, 4, TimeUnit.SECONDS);
    

运行结果

2s后开始执行定时任务,每隔5s执行一次。

设置的是每隔4秒执行一次定时任务,为什么实际上是5s执行一次呢? 

        注意,如果执行任务时间大于设置的定时任务执行时间,那么此方法会以执行任务的时间为准,简而言之,就是哪个时间长就以哪个时间作为定时任务执行的周期。

        

        scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的。

示例代码

public class ThreadPoolDemo5 
    public static void main(String[] args) 
        // 创建线程池
        ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
        System.out.println("添加任务执行时间:" + LocalDateTime.now());

        // 2s之后开始执行定时任务,每次执行间隔4秒
        service.scheduleWithFixedDelay(() -> 
            System.out.println("执行了任务:" + LocalDateTime.now());
            try 
                Thread.sleep(5 * 1000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
        , 2, 4, TimeUnit.SECONDS);
    

运行结果

 2s后开始执行任务,每隔9秒执行一次定时任务。

 为什么这个也不是每隔4s执行一次,而是9s呢???

         因为  scheduleWithFixedDelay 是以上⼀次任务的结束时间,作为下次定时任务的参考时间的,上个任务执行5s后,再延时4s执行延时任务。

3.4 定时任务单线程(Executors.newSingleThreadScheduledExecutor

示例代码

public class ThreadPoolDemo6 
    public static void main(String[] args) 
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        System.out.println("添加任务时间:" + LocalDateTime.now());
        service.schedule(() -> System.out.println("执行任务:" + LocalDateTime.now()), 2, TimeUnit.SECONDS);
    

运行结果

3.5 单线程线程池(Executors.newSingleThreadExecutor

 示例代码

public class ThreadPoolDemo7 
    public static void main(String[] args) 
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) 
            int finalI = i;
            service.submit(() -> System.out.println("任务:" + finalI + ", 线程名:" + Thread.currentThread().getName()));
        
    

 运行结果

单线程的线程池有什么意义呢?

  1.  自定义拒绝策略;
  2. 提供了任务队列和任务管理的功能。

3.6 根据当前CPU生成线程池(Executors.newWorkStealingPool

示例代码

public class ThreadPoolDemo8 
    public static void main(String[] args) 
        ExecutorService service = Executors.newWorkStealingPool();
        for (int i = 0; i < 100; i++) 
            service.submit(() -> System.out.println("线程名:" + Thread.currentThread().getName()));
        
        while (!service.isTerminated())
        
    

运行结果

 

3.7 手动方式(ThreadPoolExecutor) 

3.7.1 创建忽略最新任务的线程池

示例代码

public class ThreadPoolDemo11 
    public static void main(String[] args) 
        ThreadFactory factory = r -> 
            Thread thread = new Thread(r);
            return thread;
        ;

        // 手动方式创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
                        new LinkedBlockingDeque<>(2), factory, new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 5; i++) 
            int finalI = i;
            executor.submit(() -> 
                try 
                    Thread.sleep(finalI * 100);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println(Thread.currentThread().getName() + "执行任务" + finalI);
            );
        
        // 终止线程池
        executor.shutdown();
    

运行结果

 

 3.7.2 ThreadPoolExecutor 参数说明

  1. corePoolSize核心线程数,可以大致理解为长期驻留的线程数目(除非设置了allowCoreThreadTimeOut)。对于不同的线程池,这个值可能会有很大区别,比如newFixedThreadPool 会将其设置为 nThreads,而对于 newCachedThreadPool 则是为 0。
  2. maximumPoolSize顾名思义,就是线程不够时能够创建的最⼤线程数。同样进⾏对比,对于newFixedThreadPool,当然就是 nThreads,因为其要求是固定大小,而 newCachedThreadPool 则是 Integer.MAX_VALUE。
  3. keepAliveTime空闲线程的保活时间,如果线程的空闲时间超过这个值,那么将会被关闭。注意此值生效条件必须满足:空闲时间超过这个值,并且线程池中的线程数少于等于核⼼线程数corePoolSize。当然核心线程默认是不会关闭的,除非设置了allowCoreThreadTimeOut(true)那么核心线程也可以被回收。
  4. TimeUnit时间单位。
  5. BlockingQueue任务队列,用于存储线程池的待执行任务的。
  6. threadFactory⽤于生成线程,⼀般我们可以⽤默认的就可以了。
  7. handler当线程池已经满了,但是又有新的任务提交的时候,该采取什么策略由这个来指定。有几种方式可供选择,像抛出异常、直接拒绝然后返回等,也可以自己实现相应的接口实现自己的逻辑。

3.7.3 线程池执行流程

3.7.4 拒绝策略(5种(4(JDK提供的) + 1(自定义拒绝策略)))

 JDK提供的四种拒绝策略:

  1. DiscardPolicy : 忽略旧任务(队列第一个任务)
  2. AbortPolicy : 提示异常,拒绝执行(默认的拒绝策略)
  3. CallerRunsPolicy : 使用调用线程池的线程来执行任务
  4. DiscardOldestPolicy :  忽略最新任务 

自定义拒绝策略

示例代码

public class ThreadPoolDemo10 
    public static void main(String[] args) 
        ThreadFactory factory = r -> 
            Thread thread = new Thread(r);
            return thread;
        ;

        // 手动方式创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
                        new LinkedBlockingDeque<>(2), factory,
                        new RejectedExecutionHandler() 
                            @Override
                            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) 
                                // 自定义拒绝策略
                                System.out.println("自定义拒绝策略");
                            
                        );
        for (int i = 0; i < 5; i++) 
            int finalI = i;
            executor.submit(() -> 
                try 
                    Thread.sleep(finalI * 100);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println(Thread.currentThread().getName() + "执行任务" + finalI);
            );
        
        // 终止线程池
        executor.shutdown();
    

运行结果:

 

4. 线程池状态

 查看 ThreadPoolExecutor 源码可知线程的状态如下:

  • RUNNING:这是最正常的状态:接受新的任务,处理等待队列中的任务;
  • SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务;
  • STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程;
  • TIDYING:所有的任务都销毁了,workCount 为 0。线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated(); 
  • TERMINATED:terminated() 方法结束后,线程池的状态就会变成这个。

各个状态的转换过程有以下几种:

  • RUNNING -> SHUTDOWN:当调用了 shutdown() 后,会发生这个状态转换,这也是最重要的
  • (RUNNING or SHUTDOWN) -> STOP:当调⽤ shutdownNow() 后,会发⽣这个状态转换;
  • SHUTDOWN -> TIDYING:当任务队列和线程池都清空后,会由 SHUTDOWN 转换为 TIDYING;
  • STOP -> TIDYING:当任务队列清空后,发⽣这个转换;
  • TIDYING -> TERMINATED:这个前面说了,当 terminated() 方法结束后。

shutdown VS shutdownNow :

  • shutdown 执行时线程池终止接收新任务,并且会将任务队列中的任务处理完;
  • shutdownNow 执行时线程池终止接收新任务,并且会给终止执行任务队列中的任务。

shutdown

public class ThreadPoolDemo10 
    public static void main(String[] args) 
        ThreadFactory factory = r -> 
            Thread thread = new Thread(r);
            return thread;
        ;

        // 手动方式创建线程池
        ThreadPoolExecutor executor =
                new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
                        new LinkedBlockingDeque<>(2), factory,
                        new RejectedExecutionHandler() 
                            @Override
                            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) 
                                // 自定义拒绝策略
                                System.out.println("自定义拒绝策略");
                            
                        );
        for (int i = 0; i < 5; i++) 
            int finalI = i;
            executor.submit(() -> 
                try 
                    Thread.sleep(finalI * 100);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
                System.out.println(Thread.currentThread().getName() + "执行任务" + finalI);
            );
        
        // 终止线程池
        executor.shutdown();
    

运行结果:

 shutdownNow 

5. 究竟选用哪种线程池

学习了这么多创建线程池的方式,究竟改用哪一种呢?

阿里巴巴《Java开发手册》给我们的答案:

        所以综上情况所述,我们推荐使用 ThreadPoolExecutor 的方式进行线程池的创建,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避⼀些未知的风险。

以上是关于java线程池(详解)的主要内容,如果未能解决你的问题,请参考以下文章

Java线程池七个参数详解

Java自定义线程池详解

java线程池详解

java线程池ThreadPoolExecutor类使用详解

java线程池ThreadPoolExecutor类使用详解

java线程池ThreadPoolExecutor类使用详解