来自【阿里内部】并发编程学习笔记解密分享

目录

一、引言

二、JDK提供的原生线程池

三、深入源码剖析线程池工作原理

execute:

addWoker:

四、深入源码分析线程池线程复用原理

五、自定义线程池实战

五、线程池参数合理配置

六、参考


一、引言

  • 一般在开发过程中,一个功能是运行时长太久了,一般是通过什么方式去优化的?
    异步/多线程,对于一个业务方法而言,如果其中的调用链太长势必会引起程序运行时间延长,导致整个系统吞吐来量下降,而我们使用多线程方式来对该方法的调用链进行优化,对于一些耦合度不是特别高的调用关系可以直接通过多线程来走异步的方式进行处理,大大的缩短了程序的运行时长,但是如果我们的多线程创建方式是通过 new Thread();这种方式去进行显式创建的话它真的可以吗?答案是不可以,Why?答案如下:

  • 如果在生产环境使用new Thread();这种方式去进行显式创建线程会带来什么后果?

    • 1. OOM: 如果当前方法突遇高并发情况,假设此时来了1000个请求,而按传统的网络模型是BIO,此时服务器会开1000个线程来处理这1000个请求(不考虑WEB容器的最大线程数配置),当1000个请求执行时又会发现此方法中存在new Thread();创建线程,此时每个执行请求的线程又会创建一个线程,此时就会出现1000*2=2000个线程的情况出现,而在一个程序中创建线程是需要向JVM申请内存分配的,但是此时大量线程在同一瞬间向JVM申请分配内存,此时会很容易造成内存溢出(OOM)的情况发生。
    • 2. 资源开销与耗时: Java对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除。因此,对象的生命周期长度可用如下的表达式表示:Object = O1 + O2 +O3。其中O1表示对象的创建时间,O2表示对象的使用时间,而O3则表示其清除(垃圾回收)时间。由此,我们可以看出,只有O2是真正有效的时间,而O1、O3则是对象本身的开销。当我们去创建一个线程时也是一样,因为线程在Java中其实也是一个Thread类的实例,所以对于线程而言,其实它的创建(申请内存分配、JVM向OS提交线程映射进程申请、OS真实线程映射)和销毁对资源是开销非常大的并且非常耗时的。
    • 3. 不可管理性: 对于new Thread();的显示创建出来的线程是无法管理的,一旦CPU调度成功,此线程的可管理性几乎为零。
  • 那么我们使用线程池能给我们带来什么好处?

      1. 降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗。
      1. 提高响应速度:任务到达时不需要等待线程创建就可以立即执行。
      1. 提高线程的可管理性:线程池可以统一管理、分配、调优和监控。

而在Java中为我们提供四种原生线程池,它们都是基于ThreadPoolExecutor类实现的,所以ThreadPoolExecutor类这也是我们待会儿分析线程池原理时的重点~

二、JDK提供的原生线程池

在Java中,JDK通过Executors类为我们提供了四种封装好的线程池类型(ForkJoinPool不在本章探讨范围之内),源码如下:

 

//创建一个定长的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    }
//创建一个单线程的线程池
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
    }
//创建一个可缓存支持灵活回收的线程池
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
    }
//创建一个支持周期执行任务的线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

在上面的源码中,其实我们通过观察发现JDK为我们提供的四种线程池内部都是通过封装ThreadPoolExecutor类的构造函数来进行线程池的初始化的,所以我们先来理清楚线程池“家族”体系。

 

 

从上图中我们可以得知,线程池的最上层接口是Executor,而这个接口定义了一个核心方法execute(Runnable command),当我们使用它时,需要传递一个Runnable类型的异步任务作为参数。我们看一下Executor接口的定义:

 

public interface Executor {
    // 提交任务到线程池并执行的方法
    void execute(Runnable command);
}

而Executor接口是一个函数式接口,其中只定义了一个方法,但是我们在使用线程池的时候为什么能够调用的方法却会有那么多呢?因为还有一个ExecutorService接口,它继承了Executor接口作为Executor接口的子接口,为Executor接口提供了很多拓展方法。我们接着看ExecutorService接口的实现:   
```java
public interface ExecutorService extends Executor {
    // 等待线程池执行完成已接收的任何后关闭线程池,将线程池置为SHUNTDOWM状态
    void shutdown();
    // 尝试主动终止线程池中的所有正在执行的任务并返回未执行的任务列表,
    // 将线程池置为STOP状态
    List<Runnable> shutdownNow();
    // 判断线程池是否已关闭:线程池调用过shutdown或者shutdownNow后返回true
    boolean isShutdown();
    // 判断线程池中的子线程是否已全部终止
    // 当调用shutdown后全部任务执行完成返回true或调用shutdownNow成功后返回true
    boolean isTerminated();
    // 配合shutdown使用,在调用shutdown后调用该方法,让线程池在指定时间内关闭,
    // 不管任务是否执行完成,在指定时间内还在执行任务则抛出异常中断线程
    // 注意:有时能够关闭线程池单并不能完全保证线程池中子线程停止执行
    // 比如子线程中用到 BufferedReader,那么需要配合shutdownNow主动中断所有子线程
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    // 向线程池提交一个Callable类型的异步任务,当线程池执行后返回执行结果
    <T> Future<T> submit(Callable<T> task);
    // 向线程池提交一个Runnable类型的异步任务,线程池执行完成后将返回指定类型的执行结果
    <T> Future<T> submit(Runnable task, T result);
    // 向线程池提交一个Runnable类型的异步任务,线程池执行完成后执行的结果
    Future<?> submit(Runnable task);
    // 传入一个Collection类型的异步任务集合,批量执行并返回执行结果
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    // 传入一个Collection类型的异步任务集合,在指定的时间内批量执行并返回执行
    // 结果,如果超时则抛出异常中断线程
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
    // 传入一个Collection类型的异步任务集合,返回第一个执行完成的结果并终止其他线程
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    // 传入一个Collection类型的异步任务集合,在指定的时间内返回第一个执行完成的结果
    // 并终止其他线程,如果超时则抛出异常中断线程
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

通过上面的代码我们会发现ExecutorService的确继承了Executor接口,作为Executor拓展接口提供了很多其他的方法以便于开发人员使用线程池,而Executor和ExecutorService接口中的方法实现全部都是由ThreadPoolExecutor类来完成的,而ThreadPoolExecutor继承了AbstractExecutorService,我们来看一下AbstractExecutorService的实现:

 

public abstract class AbstractExecutorService implements ExecutorService {
    // 将异步任务包装为Future,传递Runnable类型异步任务,声明返回类型,返回一个RunnableFuture
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
    // 将异步任务包装为Future,传递Callable类型异步任务,返回一个RunnableFuture
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
    public Future<?> submit(Runnable task) {};
    public <T> Future<T> submit(Runnable task, T result) { };
    public <T> Future<T> submit(Callable<T> task) { };
    // 在指定的时间内执行传入的异步任务集合,返回最后一个任务执行
    //执行集合tasks结果是最后一个执行结束的任务结果
    //可以设置超时 timed为true并且nanos是未来的一个时间
    //任何一个任务完成都将会返回结果
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                              boolean timed, long nanos)
            throws InterruptedException, ExecutionException, TimeoutException {
        //传入的任务集合不能为null
        if (tasks == null)
            throw new NullPointerException();
        //传入的任务数不能是0
        int ntasks = tasks.size();
        if (ntasks == 0)
            throw new IllegalArgumentException();
        //满足上面的校验后将任务分装到一个ArrayList中
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
        //并且创建一个执行器传入this
        //这里简单讲述他的执行原理,传入this会使用传入的this(类型为Executor)作为执行器用于执行任务,当submit提交任务的时候回将任务
        //封装为一个内部的Future并且重写他的done而此方法就是在future完成的时候调用的,而他的写法则是将当前完成的future添加到esc
        //维护的结果队列中
        ExecutorCompletionService<T> ecs =
                new ExecutorCompletionService<T>(this);

        try {
            //创建一个执行异常,以便后面抛出
            ExecutionException ee = null;
            //如果开启了超时则计算死线时间如果时间是0则代表没有开启执行超时
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            //获取任务的迭代器
            Iterator<? extends Callable<T>> it = tasks.iterator();
            //先获取迭代器中的第一个任务提交给前面创建的ecs执行器
            futures.add(ecs.submit(it.next()));
            //前面记录的任务数减一
            --ntasks;
            //当前激活数为1
            int active = 1;
            //进入死循环
            for (;;) {
                //获取刚才提价的任务是否完成如果完成则f不是null否则为null
                Future<T> f = ecs.poll();
                //如果为null则代表任务还在继续
                if (f == null) {
                    //如果当前任务大于0 说明除了刚才的任务还有别的任务存在
                    if (ntasks > 0) {
                        //则任务数减一
      
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值