> 文档中心 > 线程池用法详解

线程池用法详解


线程池用法详解

前言

为什么需要用到线程池?

众所周知线程是操作系统一种宝贵的资源,创建和销毁都很耗费资源,对此,java中针对这种情况实现出线程池思想,维护一定数量的线程,用到时候就不需要去创建,不使用时候,就销毁一定量的,保留一部分线程数量。

Java线程池的工作流程

Java线程池的工作流程为:线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时,线程池会按照以下流程执行任务。

  • 如果正在运行的线程数量少于corePoolSize(用户定义的核心线程数),线程池就会立刻创建线程并执行该线程任务。

  • 如果正在运行的线程数量大于等于corePoolSize,该任务就将被放入阻塞队列中。

  • 在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务。

  • 在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时,线程池将拒绝执行该线程任务并抛出RejectExecutionException异常。

  • 在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。

  • 在线程处于空闲状态的时间超过keepAliveTime时间时,正在运行 的线程数量超过corePoolSize,该线程将会被认定为空闲线程并停止。因 此在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。

在这里插入图片描述

5种常用的线程池

JAVA中提供了5种常用的线程池

缓存线程池 newCachedThreadPool

用于创建一个缓存线程池。之所以叫缓存线程 池,是因为它在创建新线程时如果有可重用的线程,则重用它们,否则重 新创建一个新的线程并将其添加到线程池中。对于执行时间很短的任务而言,newCachedThreadPool线程池能很大程度地重用线程进而提高系统的性能。

在这里插入图片描述

适用场景:

执行时间很短的任务

特点:

执行时间很短的任务,无容量队列,提交一个任务创建一个线程。

使用 SynchronousQueue队列,特点是没有容量,没有线程是放不进去的。

没有线程任务运行时,newCachedThreadPool将不会占用系统的线程资源。

固定线程池newFixedThreadPool

在这里插入图片描述

特点:

核心线程数 == 最大线程数,无需超时销毁。
阻塞队列是无界的,可以存放任意数量的任务。

适用场景:

适用于任务量已知,相对耗时的任务。

可定时调度 newScheduledThreadPool

newScheduledThreadPool创建了一个可定时调度的线程池,可设置在给定的延迟时间后执行或者定期执行某个线程任务

在这里插入图片描述
构造
在这里插入图片描述

延迟执行任务
在这里插入图片描述
按固定频率执行,与任务本身执行时间无关。但 有个前提条件,任务执行时间必须小于间隔时间,例如间隔时间是5 s,每5s执行一次任务,任务的执行时间必须小于5s。

周期执行任务
在这里插入图片描述

WithFixedDelay:按固定间隔执行,与任务本身执行时间有 关。例如,任务本身执行时间是10s,间隔2s,则下一次开始执行的
时间就是12s。

newSingleThreadScheduledExecutor 单个线程任务调度

newSingleThreadExecutor线程池会保证永远有且只有一个可用的线程,在该线程停止或发生异常时,newSingleThreadExecutor线程池会启动一个新的线程来代替该线程继续执行任务
在这里插入图片描述
相比于TimerTask 定时任务类,发生异常不会退出,不影响执行任务。

newWorkStealingPool

JDK提供的一个新的线程池newWorkStealingPool

该线程池以上线程池构造不同,采用ForkJoin线程池,并发是使用ThreadPoolExecutor构造
在这里插入图片描述

newWorkStealingPool创建持有足够线程的线程池来达到快速运算的目
的,在内部通过使用多个队列来减少各个线程调度产生的竞争。这里所说 的有足够的线程指JDK根据当前线程的运行需求向操作系统申请足够的线 程,以保障线程的快速执行,并很大程度地使用系统资源,提高并发计算
的效率,省去用户根据CPU资源估算并行度的过程。当然,如果开发者想自
己定义线程的并发数,则也可以将其作为参数传入。

   //获取cpu核数 // System.out.println(Runtime.getRuntime().availableProcessors()); // 不指定线程大小 则是cpu默认核心数 ExecutorService executorService = Executors.newWorkStealingPool(); //可以提交 有返回 或者 无返回 Future<String> submit = executorService.submit(() -> {     TimeUnit.SECONDS.sleep(1);     return "run able"; }); Future<String> submit1 = executorService.submit(new Callable<String>() {     @Override     public String call() throws Exception {  TimeUnit.SECONDS.sleep(1);  return "call able ";     } }); System.out.println(submit.get()); System.out.println(submit1.get()); System.out.println(" ------------------- "); //可以  提交一个集合  会等全部线程运行结束 // List<Callable> list  = new ArrayList(); // executorService.invokeAny(list);

ThreadPoolExecutor构造方法

一般情况下,构造线程使用ThreadPoolExecutor来显示的指定线程池参数,包括核心线程数,最大线程数,时间,队列选择以及大小,线程工厂,拒绝策略。防止因为默认的参数任务数量大或者频繁创建线程带来系统问题。

在这里插入图片描述

线程池中提交任务方法

在这里插入图片描述

关闭线程池

shutdown()

非阻塞方法

//打断    // 20 个线程    // 10 个空闲 会打断线程 并且标记    // 10个工作  等待工作执行完成
    public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try {     checkShutdownAccess();//设置SHUTDOWN 标志 打断空闲线程,设置执行线程中断标志,等待任务运行结束     advanceRunState(SHUTDOWN);      interruptIdleWorkers();  //打断空闲线程     onShutdown(); // hook for ScheduledThreadPoolExecutor } finally {     mainLock.unlock(); } tryTerminate(); //尝试在打断    }
shutdownNow()

非阻塞方法

// 20个线程    // 10个在队列里    // 1 、会立即将队列里任务打断返回(这些不会处理完)    // 2 、打断工作中10个线程,最后执行任务中的10个线程 等待执行完成(处理完)    // 3 、 20个线程退出
    public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try {     checkShutdownAccess();     advanceRunState(STOP);  //设置stop 标志 打断  执行和没执行的线程都打断     interruptWorkers();  //打断线程     tasks = drainQueue(); } finally {     mainLock.unlock(); } tryTerminate(); return tasks;  //返回队列中任务    }

  List<Runnable> runnables = executorService.shutdownNow();  //返回任务 System.out.println(runnables); System.out.println(runnables.size());
后台线程方式停止线程池

需要自定义线程池 线程工厂

   ExecutorService executorService = new ThreadPoolExecutor(10,20,30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),  //线程工厂 参数  r ->{     Thread t = new Thread(r);     t.setDaemon(true);     return t;  },new ThreadPoolExecutor.AbortPolicy());

自定义线程工厂,设置主线程是后台线程, 线程池等待时间 5s 同时线程组也是后台线程,整个线程池就会一起退出!

最多等待时间打断

在这里插入图片描述
设置等待时间,最多等待多少时间,没有执行完,则销毁线程池。
executorService.awaitTermination(5,TimeUnit.SECONDS);

对于非返回参数任务而言,想知道每个线程是否执行完成,这个时候,配合CountDownLatch使用。

拒绝策略

当最大线程池和队列都满的时候,此时再来任务,这时候就需要做出抉择,是抛弃任务还是怎么办。线程池中提供了4中策略。

AbortPolicy

线程池默认就是AbortPolicy直接抛出异常,阻止线程正常运行

CallerRunsPolicy

CallerRunsPolicy的拒绝策略为:如果被丢弃的线程任务未关闭,则 执行该线程任务。

DiscardOldestPolicy

移除线程队列中最早的一个线程任务,并尝试提交当前任务。

DiscardPolicy

DiscardPolicy的拒绝策略为:丢弃当前的线程任务而不做任何处理。如果系统允许在资源不足的情况下丢弃部分任务,则这将是保障系统安
全、稳定的一种很好的方案。

当然也可以自定义策略

上面四种都实现了RejectedExecutionHandler接口,若无法 满足实际需要,则用户可以自己扩展RejectedExecutionHandler接口来实 现拒绝策略,并捕获异常来实现自定义拒绝策略。

线程池异常处理

线程池异常处理

线程大小如何选择

线程池最核心的东西是线程数量选择,一般而言,线程数量选择取决去该任务是计算型还是IO型任务。

理论而言

计算型
线程数量 = CPU核心数 + 1

IO型
线程数量 = 2 * CPU核心数 + 1

IO型还有一种计算公式
线程数量 = 核心数 * 期望CPU利用率 * 总时间(CPU计算时间 + 等待时间) / CPU 计算时间

当然具体参数是需要根据实际任务和CPU处理能力压测动态调节线程大小的。