线程池ThreadPoolExecutor详解

线程池是什么

线程池是对一组线程的管理和运用。如果不运用线程池来管理我们的线程,那么我们自己频繁的new线程池会影响程序运行的效率,首先一点,Thread也是一个对象,只要是对象

就有可能会被垃圾回收,频繁回收会带来系统卡顿。而且在实际业务中,我们并不想随意丢弃Thread对象,所以这时候,线程池技术就应运而生了。

线程池的基本用法

略。。。。

线程池执行任务的具体流程是怎样的

线程池提供了两种方法执行任务

void execute(Runnable task)
Future<?> submit(Runnable task)

 

实际上submit调用的还是execute方法,只不过会返回一个Future对象,用来获取任务执行的结果

1 public <T> Future<T> submit(Callable<T> task) {
2         if (task == null) throw new NullPointerException();
3         RunnableFuture<T> ftask = newTaskFor(task);
4         execute(ftask);
5         return ftask;
6     }

execute方法执行会分为三步,用个流程图就可以看懂了

 

 

注意

1.提交一个Runnable时候,不管当前线程池中的线程是否空闲,只要数量小于核心线程数就会创建新线程

2.线程池相当于是非公平的,比如队列满了之后提交的任务可能会比正在排队的任务先执行(看流程图就懂了)

 

 

线程池的五种状态

1.RUNNING: 会接受新任务并且会处理队列中的任务

2.SHUTDOWN: 不会接收新任务并且会处理队列中的任务

3.STOP: 不会接收新任务,并且不会处理队列中的任务,并且会中断在处理的任务(能不能被中断得看任务本身)

4.TIDYING: 所有任务都终止了,线程池中也没有线程了,这样线程池的状态就会转为TIDYING,一旦达到此状态,就会调用线程池的terminated方法

5.TERMINATED: teminated方法执行完后机会转变为 TERMINATED

 

这五种状态并不能任意转换,只会有以下几种情况:

1.RUNNING->SHUTDOWN:手动调用线程池的shutdown()方法,或者线程池对象GC时会调用finalize()从而调用shutdown()

2.RUNNING、SHUTDOWN->STOP.调用shutdownNow()触发,如果先调shutdown()然后调用shutdownNow(),就会发生SHUTDOWN->STOP

3.SHUTDOWN->TIDYING: 队列为空,并且线程池中没有线程时自动转换

4.STOP->TIDYING: 线程池中没有线程时自动转换(队列中可能还有任务)

5.TIDYING->TERMINATED: terminated方法执行完后

 

线程池中的线程时如何关闭的

线程想要关闭或则说消亡,要么是任务结束自然消亡,要么是调用线程的中断方法,但是中断方法并不会立即终止线程,而是设置一个标志位

通过标志位然后判断进行线程的关闭。我们来个demo演示下interrupt方法

 

复制代码
 1 public class InterruptedTest {
 2 
 3     private static int num = 0;
 4 
 5     public static void main(String[] args) throws Exception {
 6         Thread t1 = new Thread(() -> {
 7             while (true) {
 8                 if (Thread.currentThread().isInterrupted()) {
 9                     System.out.println("线程被中断了");
10                     //这样线程就自然销毁了
11                     break;
12                 }
13                 if (num++ == 10000) {
14                     Thread.currentThread().interrupt();
15                     System.out.println("num = " + num);
16                 }
17             }
18         });
19         t1.start();
20 
21         //保证t1执行完毕
22         t1.join();
23     }
24 }
复制代码

 

 

线程池就是使用这种中断机制使线程消亡的

 

线程池中为什么要使用阻塞队列

原因有很多。线程池中的线程在运行过程中,执行完创建线程时绑定的第一个任务后,就会不断地从队里中获取任务并执行,那么如果队列中没有任务了

线程为了不自然消亡,就会阻塞在获取队列任务时,等着队列中有任务过来就会拿到任务从而去执行任务。

通过这种方法最终能确保线程池中的核心线程存活,关键代码:

getTask():

Runnable r = timed ?
//poll会在指定的超时时间获取不到任务后返回null,那么没有任务线程就自然销毁了
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
//核心线程就是阻塞在这里,因为take方法一直会阻塞
                    workQueue.take();

 

线程池中的线程执行任务发生异常会被移除线程池吗

会,但是如果所有的线程在执行任务时都出错了,会不会导致线程池中没有任何线程了?

在源码中,当执行任务出现异常时候,最终会执行processWorkerExit(),执行完这个方法后,当前线程也就自然消亡了,但是这个方法会额外再新增一个线程,这样

就能维持住固定的核心线程数。

那么现在我们扩展一个问题,线程池在执行任务过程中发生了异常,我们该如何处理呢?

处理的方式很多,比如在任务代码快中用try...catch来捕获异常,然后进行处理,还有种方式就是使用线程工厂来处理,我们来看一段代码:

复制代码
public class ThreadPoolExceptionAware {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 20, 100,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(100), new MyThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

        executor.execute(() -> {
            int i = 1 / 0;
            System.out.println("hello");
        });
    }
}

class MyThreadFactory implements ThreadFactory {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        //设置线程的异常回调
        thread.setUncaughtExceptionHandler((t, e) -> {
            System.out.println(t.getName() + "线程执行任务发生异常,异常信息为:" + e.getMessage());
        });
        return thread;
    }
}
复制代码

 

怎么设置线程池参数

对于这个问题,网上有各种各样的公式,还分CPU密集型和io密集型任务来设置不同的参数。

我的想法很简单,你的业务并发量确实过大,那么核心线程数和最大线程池数都可以设置大一点,核心线程数可以设置成当前计算机的线程数*2,最大线程数可以设置更大

。核心线程我们稍微设置的小一点,因为一般来讲核心线程我们不会消亡。那么并发量过大,我们也还要把阻塞队列设置的大一点,万一线程忙不过来,队列还可以顶一会。

那么对于超时时间,如果核心线程数设置的比较大,我们可以将超时时间设置的小一点,让非核心线程尽快消亡减少系统资源的压力。

具体数值到底该设置多少,最好还是要进行压测进行调整。

 

posted @   诸葛匹夫  阅读(66)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示