jdk并发包下:使用java.util.concurrent.Executor线程池

多线程,线程池Executor的接口类图:
jdk并发包下:使用java.util.concurrent.Executor线程池_第1张图片

其他都不重要,就ExecutorService是主要的:
jdk并发包下:使用java.util.concurrent.Executor线程池_第2张图片
基本上分为单纯线程池和定时任务线程池:
jdk并发包下:使用java.util.concurrent.Executor线程池_第3张图片
说白了除了ForkJoinPool所有都继承于ThreadPoolExecutor或者是对ThreadPoolExecutor的包装类:

//构造函数
    public ThreadPoolExecutor(
            int corePoolSize,//从0增加,直到维持不变的线程数
            int maximumPoolSize,//最大创建的线程数,比corePoolSize多出来的是多余线程数,如果空闲会被释放
            long keepAliveTime,//空闲多久释放
            TimeUnit unit,//keepAliveTime的单位
            BlockingQueue<Runnable> workQueue,//任务队列对象
            ThreadFactory threadFactory,//线程生产工厂
            RejectedExecutionHandler handler//压力大时如何处理拒绝任务
    ) {

    }

上面构造方法的各个参数的默认值:

  • corePoolSize:一般是1或者cpu核心数或者其他定量
  • maximumPoolSize:一般是0或者无限大
  • keepAliveTime:一般是0或者60s
  • unit:一般是TimeUnit.SECONDS
  • workQueue:一般是AbstractQueue 的子类
  • threadFactory:一般是DefaultThreadFactory implements ThreadFactory实例

Executors提供的类实例化方法:

        int corePoolSize = 4;
        
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(corePoolSize);

        ExecutorService executorService1 = Executors.newSingleThreadExecutor();

        ExecutorService executorService2 = Executors.newFixedThreadPool(corePoolSize);
        
        ExecutorService executorService4 = Executors.newCachedThreadPool();

        ExecutorService executorService3 = Executors.newWorkStealingPool(corePoolSize);

它们主要区别就是实例化的参数不同,比如固定的线程数是多少,并发高峰时的线程数能达到多少,空闲线程的存活时间不同,使用的保存任务的队列类不同,繁忙时多余任务的拒绝策略,等等。

需要根据实际并发需求和特点来选择不同的实例,或者自己实例化ThreadPoolExecutor直接使用。

创建好线程池后的调用就是submit(异步返回结果)和execute(不返回结果),其他方法 也简单不说了。

说下最关心的ThreadPoolExecutor的参数对性能的影响:
过程如下:

当线程池初始化完成之后,而且当前线程数量小于corePoolSize,新来的任务直接通过创建线程来直接运行,并且线程运行后不会销毁。
当线程池中正在运行的线程达到 corePoolSize 个时,不会继续创建线程,任务会放到 taskQueue 中排队等候;
当 taskQueue(阻塞队列)的容量达到上限(即队列中不能再加入任务线程了),而且设置的maximumPoolSize大于corePoolSize时,则新增额外线程来处理任务
taskQueue 的容量达到上限,且 当前线程数poolSize 达到maximumPoolSize,那么线程池已经达到极限,会根据饱和策略RejectedExecutionHandler处理新的任务

测试验证:
我设置核心线程数为4个,最大线程数为10个,队列为10个满容量,拒绝策略为抛异常:

        int corePoolSize = 4;
        int maximumPoolSize = 10;
        long keepAliveTime = 10L;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
                maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
  ,getActiveCount=0     ,getPoolSize=0    ,getTaskCount=0    ,getLargestPoolSize=0   ,getQueueSize=0
  ,getActiveCount=1     ,getPoolSize=1    ,getTaskCount=1    ,getLargestPoolSize=1   ,getQueueSize=0
  ,getActiveCount=2     ,getPoolSize=2    ,getTaskCount=2    ,getLargestPoolSize=2   ,getQueueSize=0
  ,getActiveCount=3     ,getPoolSize=3    ,getTaskCount=3    ,getLargestPoolSize=3   ,getQueueSize=0
  ,getActiveCount=4     ,getPoolSize=4    ,getTaskCount=4    ,getLargestPoolSize=4   ,getQueueSize=0
  ,getActiveCount=4     ,getPoolSize=4    ,getTaskCount=5    ,getLargestPoolSize=4   ,getQueueSize=1
  ,getActiveCount=4     ,getPoolSize=4    ,getTaskCount=6    ,getLargestPoolSize=4   ,getQueueSize=2
  ,getActiveCount=4     ,getPoolSize=4    ,getTaskCount=7    ,getLargestPoolSize=4   ,getQueueSize=3
  ,getActiveCount=4     ,getPoolSize=4    ,getTaskCount=8    ,getLargestPoolSize=4   ,getQueueSize=4
  ,getActiveCount=4     ,getPoolSize=4    ,getTaskCount=9    ,getLargestPoolSize=4   ,getQueueSize=5
  ,getActiveCount=4     ,getPoolSize=4   ,getTaskCount=10    ,getLargestPoolSize=4   ,getQueueSize=6
  ,getActiveCount=4     ,getPoolSize=4   ,getTaskCount=11    ,getLargestPoolSize=4   ,getQueueSize=7
  ,getActiveCount=4     ,getPoolSize=4   ,getTaskCount=12    ,getLargestPoolSize=4   ,getQueueSize=8
  ,getActiveCount=4     ,getPoolSize=4   ,getTaskCount=13    ,getLargestPoolSize=4   ,getQueueSize=9
  ,getActiveCount=4     ,getPoolSize=4   ,getTaskCount=14    ,getLargestPoolSize=4  ,getQueueSize=10
  ,getActiveCount=5     ,getPoolSize=5   ,getTaskCount=15    ,getLargestPoolSize=5  ,getQueueSize=10
  ,getActiveCount=6     ,getPoolSize=6   ,getTaskCount=16    ,getLargestPoolSize=6  ,getQueueSize=10
  ,getActiveCount=7     ,getPoolSize=7   ,getTaskCount=17    ,getLargestPoolSize=7  ,getQueueSize=10
  ,getActiveCount=8     ,getPoolSize=8   ,getTaskCount=18    ,getLargestPoolSize=8  ,getQueueSize=10
  ,getActiveCount=9     ,getPoolSize=9   ,getTaskCount=19    ,getLargestPoolSize=9  ,getQueueSize=10
 ,getActiveCount=10    ,getPoolSize=10   ,getTaskCount=20   ,getLargestPoolSize=10  ,getQueueSize=10
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.exemple.Test6$task@63947c6b rejected from java.util.concurrent.ThreadPoolExecutor@2b193f2d[Running, pool size = 10, active threads = 10, queued tasks = 10, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.exemple.Test6.main(Test6.java:30)

从上面输出可以解析,

  • 前4个任务,线程数一直增长从1到4,说明小于corePoolSize时一直创建线程
  • 5到14个任务,线程数不变,队列添加从1到10,说明大于corePoolSize时加入队列等待
  • 14到20个任务,队列不变,线程数又从4增长到10,说明队列满时又创建线程直到达到最大线程数
  • 第21个任务,抛出异常,因为最大10线程+最大队列10容量<21,说明线程和队列都达到最大值后,根据拒绝策略处理。

ThreadPoolExecutor线程池自身是线程安全的,但是对于执行的任务并不保证线程安全,也没有任何线程同步操作。需要用户自己处理线程安全。


以后看性能如何再改下面的猜测:
个人就基于并发峰值、任务平均处理时间等等,猜测创建线程池各个参数的合理区间:

平均并发数 高峰并发数 任务执行时间 合理参数配置: 说明
固定线程数=低合理值(CPU核心数)
最大线程数=2倍CPU核心
空闲线程等待时间=60s
队列容量=较大合理值
拒绝策略=队列不会满用不到
并发不高,不需要运行太多线程,
任务易处理,多出来全部放队列即可
很长 固定线程数=低合理值(CPU核心数)
最大线程数=较大合理值
空闲线程等待时间=60s
队列容量=中等合理值
拒绝策略=实际需要
并发不高,不需要运行太多线程,
任务时间长,尽可能利用线程数
极高 固定线程数=低合理值
最大线程数=较高合理值
空闲线程等待时间=60s
队列容量=防止内存溢出较大值
拒绝策略=还不满足,考虑买设备
平时并发不高,不需要运行太多线程,
任务易处理,并发高峰放队列和新线程
很高 极高 固定线程数=中等合理值
最大线程数=较高合理值
空闲线程等待时间=60s
队列容量=防止内存溢出最大值
拒绝策略=重要任务不抛弃,最大化利用内存和cpu资源
平时并发高,需要运行较多线程,
任务易处理,提升队列容量
很高 极高 很长 固定线程数=中等合理值
最大线程数=较高合理值
空闲线程等待时间=60s
队列容量=防止内存溢出最大值
拒绝策略=重要任务不抛弃,最大化利用内存和cpu资源
长时间任务,应该考虑另外使用任务调度容器来执行。

参数设置依据:

  • 平常并发操作一般的话,线程数不是越高越好,相对于物理真实的线程数和线程时间片时间长度,此参数设置应该合理区间,不然实质上会经常切换线程调度,消耗切换时间和资源。
  • 为了提高线程池处理能力,如果设置队列容量过大,当真的有大批任务过来,可能导致内存溢出。而且队列过大,就不再触发最大线程数这个设置,一直都是固定线程处理任务。
  • Executors提供的四种创建线程池的参数配置,都是特别对应不同场景的较好设置值,值得参考。

你可能感兴趣的:(jdk并发包下:使用java.util.concurrent.Executor线程池)