ThreadPoolExecutor类详解

使用线程池的目的是:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。所以需要统一管理

1.构造和参数

在ThreadPoolExecutor类中四个构造方法:

Java项目中 全局线程池 java线程池最佳实践_线程池


前三个都是调第四个构造方法,参数也最多如下

Java项目中 全局线程池 java线程池最佳实践_后端_02

  • corePoolSize:核心线程数,会一直存活,即使没有任务,线程池也会维护线程的最少数量
  • maximumPoolSize:线程池维护线程的最大数量
  • keepAliveTime:线程池维护线程所允许的空闲时间,当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
  • unit:线程池维护线程所允许的空闲时间的单位,参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
  • Java项目中 全局线程池 java线程池最佳实践_开发语言_03

  • workQueue:一个阻塞队列,用来存储等待执行的任务,常用队列如下
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
  • threadFactory:线程工厂,主要用来创建线程
  • handler:线程池中的数量大于maximumPoolSize,对拒绝任务的处理策略,取值如下
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

2.调用关系

Java项目中 全局线程池 java线程池最佳实践_Java项目中 全局线程池_04


抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法

Java项目中 全局线程池 java线程池最佳实践_后端_05


ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等

Java项目中 全局线程池 java线程池最佳实践_java_06

Executor是顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的

Java项目中 全局线程池 java线程池最佳实践_Java项目中 全局线程池_07

3.ThreadPoolExecutor类中重要方法

  • execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
  • submit()用来向线程池提交任务,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用Future来获取任务执行结果
  • shutdown():不会立即关闭线程池,但也不接受新的任务,等待队列中所有任务执行完毕后关闭。
  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,清空工作队列,返回尚未执行的任务。
  • 其他的方法:getQueue(),getPoolSize(),getActiveCount(),getCompletedTaskCount()等获取与线程池相关属性的方法

线程池实现原理

Java项目中 全局线程池 java线程池最佳实践_java_08

线程池状态

Java项目中 全局线程池 java线程池最佳实践_后端_09


Worker

Java项目中 全局线程池 java线程池最佳实践_Java项目中 全局线程池_10


实现runnable,继承aqs,处理线程调度用的,具体源码自己去看比较多

execute()源码

//通过execute向线程池提交任务public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();    //如果当前线程数未达到核心线程数,则直接创建线程来执行新任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }    //否则将任务加入阻塞队列,这里进行双重检查,如果线程池已经关闭,则调用reject(),    //如果当前线程池线程数为0,则新建线程
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }    //如果加入阻塞队列失败,则尝试新建一个线程,如果失败了    //则说明线程池关闭了或者线程达到最大线程数,因此调用reject()
    else if (!addWorker(command, false))
        reject(command);
}

简单实例

public class Test {
     public static void main(String[] args) {   
         ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
                 new ArrayBlockingQueue<Runnable>(5));
          
         for(int i=0;i<15;i++){
             MyTask myTask = new MyTask(i);
             executor.execute(myTask);
             System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
             executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
         }
         executor.shutdown();
     }
}
 
 
class MyTask implements Runnable {
    private int taskNum;
     
    public MyTask(int num) {
        this.taskNum = num;
    }
     
    @Override
    public void run() {
        System.out.println("正在执行task "+taskNum);
        try {
            Thread.currentThread().sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task "+taskNum+"执行完毕");
    }
}

结果

Java项目中 全局线程池 java线程池最佳实践_后端_11

当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程

使用线程池实现异步工具

@Component
@Slf4j
public class AsyncUtils {

    private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(1, 5, 60,
            TimeUnit.MINUTES, new ArrayBlockingQueue<>(512));
    @Resource
    private AsyncTaskMapper asyncTaskMapper;

    public void execute(Runnable runnable, String param, String taskDesc) {
        AsyncTaskDO asyncTaskDO = AsyncTaskDO.builder()
                .param(param)
                .taskDesc(taskDesc)
                .build();
        asyncTaskMapper.insert(asyncTaskDO);
        EXECUTOR.submit(new TenantRunnableWrap(runnable, asyncTaskMapper, asyncTaskDO));

    }

    @Slf4j
    static class TenantRunnableWrap implements Runnable {
        private final Runnable task;
        private final AsyncTaskMapper asyncTaskMapper;
        private final AsyncTaskDO asyncTaskDO;

        public TenantRunnableWrap(Runnable task, AsyncTaskMapper asyncTaskMapper, AsyncTaskDO asyncTaskDO) {
            this.task = task;
            this.asyncTaskMapper = asyncTaskMapper;
            this.asyncTaskDO = asyncTaskDO;
        }

        @Override
        public void run() {
            try {
                task.run();
            } catch (Exception e) {
                asyncTaskDO.setExceptionDesc(e.getMessage());
                asyncTaskMapper.updateById(asyncTaskDO);
                return;
            }
            asyncTaskMapper.deleteById(asyncTaskDO);
        }
    }
}

使用方法如下,传参的目的是统计出异常的时候存表,对于异步任务不好解决报错提供了一个思路

Java项目中 全局线程池 java线程池最佳实践_开发语言_12

如何合理配置线程池的大小

Java项目中 全局线程池 java线程池最佳实践_开发语言_13

阿里巴巴Java开发手册中明确指出,而且用的词是『不允许』使用Executors创建线程池:

不允许使用Executors创建线程池

Java项目中 全局线程池 java线程池最佳实践_线程池_14

【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors各个方法的弊端:

1)newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
  
2)newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

Positive example 1:
    //org.apache.commons.lang3.concurrent.BasicThreadFactory
    ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
        new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
       
            
Positive example 2:
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();
 
    //Common Thread Pool
    ExecutorService pool = new ThreadPoolExecutor(5, 200,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
 
    pool.execute(()-> System.out.println(Thread.currentThread().getName()));
    pool.shutdown();//gracefully shutdown
           
            
Positive example 3:
    <bean id="userThreadPool"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <property name="corePoolSize" value="10" />
        <property name="maxPoolSize" value="100" />
        <property name="queueCapacity" value="2000" />
 
    <property name="threadFactory" value= threadFactory />
        <property name="rejectedExecutionHandler">
            <ref local="rejectedExecutionHandler" />
        </property>
    </bean>
    //in code
    userThreadPool.execute(thread);