总结 java线程池及其用法

文章目录

  • 一、常见线程池
      • 1. 只有一个线程的线程池
      • 2. 固定数量线程的线程池
      • 3. 可以缓存空闲线程的线程池
      • 4. 可以延时/定时的线程池
  • 二、ThreadPoolExecutor
      • 三、使用场景。
      • 四、自定义线程池demo

一、常见线程池

并发编程离不开线程的使用,线程离不开线程池的使用。这里简单总结下ThreadPoolExecutor的参数及场景。
Executors 是 JUC提供的线程池使用工具类,里面定义了四种线程池的生成方法,我们从这里入手进行解释。

1. 只有一个线程的线程池

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

singleThreadExecutor是只有一个线程的线程池,十分干脆没啥说的,下图是newSingleThreadExecutor方法内部实现,ThreadPoolExecutor构造参数里面的第一个corePoolSize和maximumPoolSize都是1,这两个参数分别代表核心线程数最大线程数
总结 java线程池及其用法_第1张图片

2. 固定数量线程的线程池

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);

fixedThreadPool 是固定线程数量的线程池,核心线程数最大线程数都是这个固定数量,除此之外与singleThreadExecutor 完全一样。
总结 java线程池及其用法_第2张图片

3. 可以缓存空闲线程的线程池

 ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

cachedThreadPool 是可以缓存空闲线程的线程池,怎么缓存呢? ** ThreadPoolExecutor构造方法提供了一个参数叫做 keepAliveTime,这个参数表示非核心线程空闲后的存活时间,一旦空闲超过这个时间该线程就会被销毁。 **
可以回头去看上面两种线程池这个参数都是0L,表示非核心的线程在执行完任务后会直接销毁,而cachedThreadPool默认会保留60s,这就是缓存线程的原理。
总结 java线程池及其用法_第3张图片

4. 可以延时/定时的线程池

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        // 10s后再执行该任务
        scheduledExecutorService.schedule(()-> System.out.println("执行任务"),10,TimeUnit.SECONDS);
        // 第一次是0s后执行,之后每隔10s执行一次
        scheduledExecutorService.scheduleAtFixedRate(()-> System.out.println("执行任务"),0,10,TimeUnit.SECONDS);

scheduledExecutorService 与 之前三种最大的不同点在于,之前三种是通过new ThreadPoolExecutor得到的,而定时类线程池是通过new ScheduledThreadPoolExecutor得到的,ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,封装了定时的特性
延时又是如何实现的呢?ThreadPoolExecutor有一个参数叫做workQueue,我们提交一个任务到线程池后,线程池会先将任务放入到这个队列中,然后线程池再按照规则指定一个线程来执行这个任务。这里的workQueue可以设置为DelayedWorkQueue,以此实现任务的延时执行。
在这里插入图片描述
总结 java线程池及其用法_第4张图片

二、ThreadPoolExecutor

通过上面几种线程池的介绍,我们知道这些线程池都是从ThreadPoolExecutor通过一些构造参数的设置得到的,下面就来看一看这个神奇的构造函数。

 /**
         *  核心线程数、最大线程数、非核心线程数多久被销毁、keepAliveTime的单位、队列(一般为sync、有界、无界三种)、拒绝策略(默认为AbortPolicy,有长任务时可以考虑DiscardOldestPolicy)
         */
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1,10,60L
                , TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(1),new ThreadPoolExecutor.AbortPolicy());
  • corePoolSize : 核心线程数。常备线程,这些线程空闲 也不会丢弃。
  • maxiumPoolSize : 最大线程数。代表线程最多能开多少个,若是超过这个数值,新来的任务就需要执行拒绝策略
  • keepAliveTime : 空闲线程存活时间。如前面所说,非核心线程再工作完后一般是直接销毁,如果想让其保留一段时间,可以设置该参数。
  • workQueue : 工作队列,常用的有sync、ArrayBlocking、LinkedBlocking,分别代表无队列有界队列无界队列。也可以自己实现该队列,DelayQueue就是另外一种特殊的队列实现,只有队列中的元素过期了才能出队列。
  • handler : 拒绝策略。
    • AbortPolicy : 丢弃新来的任务并抛出RejectedExecutionException异常。
    • DiscardPolicy : 丢弃新来的任务,但是不抛出异常。
    • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
    • CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。

ThreadPoolExecutor的核心参数大致如上所述,但是如何组合去使用他们,在什么样的场景下使用还是个问题。

三、使用场景。

  1. 需要持续的处理大量、执行时间段的任务,如何设置线程池的参数?
    线程数设置为与cpu核心接近的数量,减少频繁的线程上下文切换。
    增长keepAliveTime或将核心线程数和最大线程数设置一样,减少频繁的线程创建。
  2. 需要处理少量、执行时间长的任务,如何设置?
    如果任务为IO密集型,cpu压力不大,可以适当加大线程数量。
    如果任务为计算密集型,cpu本身就压力很大,那么还是将线程与核心数相近,减少线程切换。
    同时需要考虑将拒绝策略设置为 DiscardOldestPolicy ,防止有任务太长时间阻塞。
  3. 任务绝大多数都是短的,少部分是长任务,如何设置?
    线程数同1,主要还是考虑拒绝策略,不能让这些少量的长任务阻塞整个线程池。

四、自定义线程池demo

 public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         *  核心线程数、最大线程数、非核心线程数多久被销毁、keepAliveTime的单位、队列(一般为sync、有界、无界三种)、拒绝策略(默认为AbortPolicy,有长任务时可以考虑DiscardOldestPolicy)
         *  ForkJoinPool 不同于其他线程池。
         */
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1,10,60L
                , TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(1),new ThreadPoolExecutor.DiscardPolicy().AbortPolicy());

        // submit可以传入callable,返回future。
        Future<Integer> future = poolExecutor.submit(() -> {
             Thread.sleep(1000);
             return 1;
        });

        while( !future.isDone() ){
            System.out.println("等待计算线程给我数据");
        }
        System.out.println( future.get() );

        if( !poolExecutor.isShutdown() ){
            poolExecutor.shutdown();
        }
     }

你可能感兴趣的:(Java)