11-Java多线程、线程池框架

线程池

一、线程池

1.1 池化技术

  • 池化技术能够减少创建对象的次数,对资源进行复用。主要是针对一些创建和销毁开销比较大的资源,比如线程,连接等。

1.2 线程池优点

  • 降低资源的消耗。降低线程创建和销毁的资源消耗;
  • 提高响应速度。线程的创建时间为T1,执行时间T2,销毁时间T3,免去T1和T3的时间
  • 提高线程的可管理性。省去用户对线程的管理;

二、线程池框架

  • JDK线程池框架比较复杂,包括一系列的接口,同时还派生出很多其他接口,比如线程执行类的Runnable,Callable
    Future,FutureTask,RunnableFutrue接口。(里面有些严格来说不算线程池,但是线程池本身和线程息息相关,线程又涉及到获取结果的Future相关接口,因此糅合人在一起比较多,这里一并梳理)

2.1 Executor体系

  • JDK线程池框架继承关系图如下:

线程池

  • 类描述
接口/类功能
Executor线程池的顶层接口,只提供了任务执行的方法
ExecutorServiceExecutor子接口。提供了更多线程池相关的管理方法,如池销毁、任务提交、异步任务提交。
ScheduledExecutorServiceExecutorService子接口。能够定时或周期执行地执行任务。
AbstractExecutorService为 ExecutorService 的任务提交方法提供了默认实现。
ThreadPoolExecutor程池类。继承自AbstractExecutorService,提供不同的线程和任务的调度策略。
ForkJoinPool一种只允许执行ForkJoinTask任务的线程池。采用Fork-Join的思想递归地拆解任务执行来充分利用资源以提升应用性能。JDK1.7加入的成员
ScheduledThreadPoolExecutor继承ThreadPoolExecutor并实现ScheduledExecutorService,因此是一种线程池,且是一种可以延迟或周期执行任务的线程池,类似java的Timer。
Executors工具类。简化创建各种线程池的工具类,用到的比较多的是创建ThreadPoolExecutor的实例

2.1 Future体系

  • 在使用线程的时候,继承Thread类或者实现Runnable接口,我们都是无法获取到线程的执行结果的。由此引入了Callable和Future来异步执行线程并支持获取
    结果。相关类继承关系图如下:

image

接口/类功能
Runnable实现线程的接口,Thread类也是该接口的实现类,不支持获取结果
Future代表异步执行的结果。提供任务结果的获取方法(get),任务的取消方法(cancel)
RunnableFuture继承Runnable和Future的接口。结合了2个接口的功能,既可以作为一个线程执行,又可以获取执行完毕后的结果
FutureTaskRunnableFuture的核心实现类。常用于异步执行任务时获取结果,执行的线程对象是一个Callable实现类
Callable实现线程的接口,支持获取结果。

三、线程池核心参数和方法

3.1 参数

参数作用默认值/建议值
int corePollSize核心线程数量无默认值,参考"5.1合理配置线程池"
int maxmumPollSoze最大线程数量无默认值,参考"5.1合理配置线程池"
long keepAliveTime空闲线程的存活时间,只在线程大于corePollSize才有效无默认值
TimeUnit unitkeepAliveTime的时间单位无默认值,
BlockingQueue workQueue任务队列。保存提交的工作任务无默认值,
ThreadFactory threadFactory线程工厂DefaultThreadFactory
RejectedExecutionHandler handler饱和拒绝策略AbortPolicy(抛出异常,拒绝执行)
  • RejectedExecutionHandler参数可选值:
RejectedExecutionHandler可选值策略
AbortPolicy默认,抛出异常
CallerRunsPolicy用调用者所在的线程来执行任务
DiscardOldestPolicy丢弃阻塞队列里最老的任务(队列里最靠前的任务)
DiscardPolicy直接丢弃当前任务
自定义实现RejectedExecutionHandler接口即可

3.2 方法

  • 提交任务
方法名备注
execute不需要返回值 (Executor顶层接口定义的方法)
submit需要返回值,返回Future接口 (ExecutorService接口定义的方法)
  • 关闭线程池
方法名备注
shutDown设置线程池状态,只中断没有执行任务的线程
shutDownNow设置线程池状态,尝试停止正在执行和暂停的线程

四、线程池转换状态

  • 前面我们给出了线程池关键的参数和几个常用方法,没有描述线程的转换状态。每一个线程都有它的生命周期,线程池管理的对象就是线程,处于池中的线程由线程池来维护和管理线程的状态,这里我们分
    析其状态转换以及任务被提交后线程池的处理策略。

4.1 线程状态变化

  • 线程池默认在首次提交任务时才会创建线程,也可以调用prestartCoreThread()方法预先创建好corePollSize个线程

4.2 任务处理策略

  • 当新的任务提交后,如果:
1.如果线程数量小于corePollSize,那么就启动线程执行该任务;
2.如果线程达到了corePollSize,那么将该任务放进阻塞队列;
3.如果队列已满,当前线程数量小于maxmumPollSoze,那么再启动新的线程执行该任务;
4.如果队列已满,当前线程数量也达到了maxmumPollSoze,那么就使用拒绝策略handler处理该线程 

4.3 示意图

  • 状态变化示意图。从图中我们可以看到,下面的四个数字就对应了4.2中描述的1-4四个步骤;

image

五、线程池的使用

5.1 合理配置线程池

任务类型示例推荐配置
Cpu(计算)密集型(使用cpu和内存)计算,大数分解,正则计算corePollSize推荐:机器核心数+1;
IO密集型文件读取,网络通信,数据库连接corePollSize推荐:2*cpu核心数(IO操作比内存和cpu的速度要满三个数量级以上)
混合型二者均有尝试拆分为上面的2种的组合(考虑拆分的成本和收益)
  • 计算密集型corePollSize推荐为核心数+1,因为有时候在调度线程时会出现页缺失,此处当前线程需要被挂起,如果线程挂起,那么超过的那一个线程会被调度。如果配置太多,线程切换可能会降低效率,带来线程切
    换的开销。
  • workQueue: 应该使用有界队列(无界队列可能会导致OOM)

5.2 预定义的线程池

5.2.1 常见线程池
线程池特点
FixedThreadPool创建固定数量的线程,使用无界队列,适用负载较重的服务器
SingleThreadExecutor创建单个线程,使用无界队列,可以保证任务顺序执行
CacheedThreadPool根据需要创建新的线程,执行很多短期异步任务,使用了SynchronousQueue
WorkStealingPool基于ForkJoinPool实现
ScheduledThreadPoolExecutor需要定期执行周期任务,不建议使用Timer
5.2.2 创建方法
  • FixedThreadPool
//Executors#newFixedThreadPool(int)
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,  new LinkedBlockingQueue<Runnable>());
    }
  • SingleThreadExecutor
//Executors#newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
    }
  • CacheedThreadPool
  • WorkStealingPool
//Executors#newWorkStealingPool(int parallelism)
public static ExecutorService newWorkStealingPool(int parallelism) {
        return new ForkJoinPool
            (parallelism,ForkJoinPool.defaultForkJoinWorkerThreadFactory,null, true);
    }

//Executors#newWorkStealingPool
public static ExecutorService newWorkStealingPool() {
        //并行度默认为CPU核心数
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
    }
  • ScheduledThreadPoolExecutor
//Executors#newSingleThreadScheduledExecutor
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
    //只含单个线程
    return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}

//Executors#newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    //含多个线程
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
  • 注意ScheduledThreadPoolExecutor有几个方法:
方法名称作用
schedule只执行一次,任务还可以延时执行
scheduleAtFixedRate提交固定时间间隔的任务,前后任务开始的时间间隔是固定的
scheduleWithFixedDelay提交固定延时间隔执行的任务,不管任务执行多久,前任务执行完毕后,到后任务开始之间的间隔是固定的

六、小结

  • 本文主要是做了线程池的框架和常见线程池的介绍,需要对线程本身有一定的基础,后面再给出一些线程池的具体使用场景和部分经典核心源码的解析。

七、参考

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值