boy-learning-thread | 1.1.6 线程池原理

相关源码:boy-learning-thread
个人博客:http://bruce.bugmakers.club
内容来自《网易微专业 - 高性能编程章节》

线程池原理

1、为什么要用线程池

线程是不是越多越好?

1、线程在 java 中是一个对象,更是操作系统中的资源,线程创建、销毁需要时间。如果 “创建时间 + 销毁时间 > 执行任务时间” 就恨不合算。

2、java 对象占用堆内存,操作系统线程占用系统内存,根据 jvm 规范,一个线程默认最大栈大小 1M,这个空间是需要从系统内存中分配的。线程过多,会消耗很多内存。

3、操作系统需要频繁切换线程上下文(大家都想被执行),线程过多会影响性能。

线程池的推出,就是为了方便的控制线程数量。

2、线程池原理 - 概念

1、线程池管理器:用于创建并管理线程池,包括:创建线程池、销毁线程池、添加新任务;

2、工作线程:线程池中的线程,在没有任务时处于等待状态,可以循环的执行任务;

3、任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了:任务的入口、任务执行完后的收尾工作、任务的状态;

4、任务队列:用于存放没有处理的任务。以供一种缓冲机制。

2.1、线程池 API - 接口定义和实现类

类型 名称 描述
接口 Executor 最上层接口,定义了执行任务的方法 execute
接口 ExecutorService 继承了 Executor 接口,拓展了 Callable、Future、关闭方法
接口 ScheduledExecutorService 继承了 ExecutorService,增加了定时任务相关的方法
实现类 ThreadPoolExecutor 基础、标准的线程池实现类
实现类 ScheduledThreadPoolExecutor 继承了 ThreadPoolExecutor,实现了 ScheduledExecutorService 中相关定时任务的方法

可以认为 ScheduledThreadPoolExecutor 是最丰富的实现类。

2.2、线程池 API - 方法定义

ExecutorService

// 监测 ExecutorService 是否已关闭,直到所有任务完成执行,或超时发生,或当前线程被中断  
awaitTermination(long timeout, TimeUnit unit);

// 执行给定的任务集合,执行完毕后,返回结果
invokeAll(Collection<? extends Callable<T>> tasks);

// 执行给定的任务集合,执行完毕或者超时后,返回结果,其他任务终止
invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);

// 执行给定的任务集合,任意一个任务执行成功后,返回结果,其他任务终止
invokeAny(Collection<? extends Callable<T>> tasks);

// 执行给定的任务集合,任意一个任务执行成功或超时后,返回结果,其他任务终止
invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit);

// 如果线程池关闭,则返回treu
isShutdown();

// 如果关闭后,所有任务都已经执行完毕,则返回true
isTerminated();

// 优雅关闭线程池,之前已经提交的任务将被执行,但不接受新的任务
shutdown();

// 尝试停止所有正在执行的任务,停止等待任务的处理,并返回等待执行任务的列表
shutdownNow();

// 提交一个用于执行的 Callable 返回任务,并返回一个 Future 对象,用于获取 Callable 执行结果
submit(Callable<T> task);

// 提交可运行任务以执行,并返回一个 Future 对象,执行结果为 null
submit(Runnable task);

// 提交可运行任务以执行,并返回一个 Future 对象,执行结果为传入的 result
sumit(Runnable task, T result);

ScheduledExecutorService

// 创建并执行一个一次性任务,过了延迟时间就会被执行
schedule(Callable<T> callable, long delay, TimeUnit unit);
schedule(Runnable command, long delay, TimeUnit unit);

// 创建并执行一个周期性任务,过了延迟时间会第一次执行,执行过程发生异常,那么任务就停止了
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit);

scheduleAtFixedRate 方法,一次任务执行时长超过周期时间,下次任务在该次任务执行完之后,立即执行。
schedualWithFixedDelay 方法,一次任务执行时间长超过周期时间,下次任务会在该次任务执行完之后,计算执行延时(再等延迟一个 period 的时长)。

2.3、线程池 API - Executors 工具类

你也可以自己实例化线程池,也可以用 Executors 创建线程池的工厂类,常用方法如下:

1) newFixedThreadPool(int nThreads)

创建一个固定大小、任务队列无界的线程池。核心线程数 = 最大线程数。

2) newCachedThreadPool()

创建一个大小无界的缓存线程池。他的任务队列是一个同步队列。
任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新的线程执行,池中的线程空闲超过60秒,将被销毁释放。
线程数随任务的多少变化。适用于任务量不可控,且执行耗时较小的异步任务。
核心线程数 = 0, 最大线程数 = Integer.MAX_VALUE

3) newSingleThreadExecutor()

只有一个线程来执行无界任务队列的单一线程池。该线程池确保任务按照加入队列的顺序一个一个一次执行。
当唯一的线程因任务异常终止时,将创建一个新的线程来执行后续的任务。
与 newFixedThreadPool(1) 的区别在于,单一线程池的大小在 new SingleThreadExecutor 方法中硬编码,不能再改变。

4) newScheduledThreadPool(int corePoolSize)

能定时执行任务的线程池。该池的核心线程数由参数来指定,最大线程数 = Integer.MAX_VALUE

3、线程池原理 - 任务 execute 过程

1、是否达到核心线程数量?没达到,创建一个工作线程来执行任务。

2、任务队列是否已满?没满,则将新提交的任务存储在任务队列里边。

3、是否达到线程池最大数量?没达到,则创建一个新的工作线程来执行任务。

4、最后,执行拒绝策略来处理这个任务。

4、线程数量

如何确定合适数量的线程?

计算型任务:cpu数量的1~2倍。

IO型任务:根据具体的IO阻塞时长进行考量决定。

如 Tomcat 中默认的最大线程数为:200

也可以考虑根据需要在一个最小数量和最大数量间自动增减线程数。

监控CPU的使用率,如果远小于80%则使用不合理,大于也不可理,接近则表示使用良好。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任...
    安仔夏天勤奋阅读 1,057评论 0 10
  • 线程池中有一定数量的工作线程,工作线程会循环从任务队列中获取任务,并执行这个任务。那么怎么去停止这些工作线程呢?这...
    wo883721阅读 1,662评论 0 14
  • 222
    Sean_9f73阅读 285评论 0 0
  • 真实经历:在之前的项目中,UI小姐姐告诉我们一位前端小姐姐说我们项目苹果手机中的边框好像不是1像素的,比1像素粗。...
    星飞822阅读 557评论 0 0
  • 几天前做了一个梦。 我在老房子里,在跟爷爷一起睡的那张床上,在爷爷身边。爷爷突然转过身,抱着我,一股爷爷的席卷而来...
    怎一个强字了得阅读 74评论 0 0