Java线程池基础

 

目录

1 概述

2 Executor框架

2.1 ThreadPoolExecutor

2.1.1 线程池状态

2.1.2 线程池增长策略

2.1.3线程池的拒绝策略

2.1.4 线程池的线程初始化

2.1.5 任务缓存队列

2.1.6 线程池的原理

2.1.7 几种线程池实现

2.1.8 线程池大小配置


1 概述

Java中经常出现多线程以及高并发的场景,线程的频繁创建在高并发及大数据量是非常消耗资源的,因此java提供了线程池。

线程池的作用:线程池就是限制系统中使用线程的数量以及更好的使用线程。创建一定数量的线程置于线程池中,这些运行着线程不断去去获取任务并运行,当没有任务时,就等待。

线程池的优点:

  1. 减少线程创建和销毁的次数,使线程可以多次复用
  2. 可以根据系统情况,调整线程的数量。防止创建过多的线程,消耗过多的内存

要讲线程池,需要先讲一下Java里的Executor框架

2 Executor框架

常用的接口与类:

Executor框架是一种将线程的创建和执行分离的机制。基于ExecutorExecutorService接口,及这两个接口的实现ThreadPoolExecutor展开。Executor有一个内部线程池,通过方法将并提供了将Runnable接口实现的任务和通过Callable接口实现的任务传递到池中线程以获得执行。

2.1 ThreadPoolExecutor

ThreadPoolExecutor类是线程池中最核心的一个类,它的构造方法如下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

构造器的参数:

  • corePoolSize核心线程数,如果运行的线程少于corePoolSize,则创建新线程来执行新任务。当线程池中的线程数达到 corePoolSize后,就会把到达的任务放到缓存队列当中
  • maximumPoolSize线程池最大线程数,表示在线程池中最多能创建多少个线程
  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。只有当线程池中的线程数大于corePoolSize时,keepAliveTime才起作用,直到线程池中的线程数不大于corePoolSize
  • unit:参数keepAliveTime的时间单位,有7种值:TimeUnit.MILLISECONDS、TimeUnit.SECONDS
  • workQueue:一个阻塞队列,用来存储等待执行的任务,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:LinkedBlockingQueue,SynchronousQueue,ArrayBlockingQueue,PriorityBlockingQueue等
  • threadFactory:线程工厂,主要用来创建线程
  • handler:表示当拒绝处理任务时的策略

ThreadPoolExecutor中,除了构造参数外,还有几个重要的成员变量:

//线程池的主要状态所,修改线程池的一些成员变量都要用这个锁,比如线程池状态,或者下面这些字段等
private final ReentrantLock mainLock = new ReentrantLock();

//worker的集合,用来存放worker
private final HashSet<Worker> workers = new HashSet<Worker>();

//设置是否允许核心线程过期,默认false,即keepalivetime对核心线程不生效
private volatile boolean allowCoreThreadTimeOut;

//记录完成的任务数量
private long completedTaskCount;

//记录线程池中出现过的最大的线程数量
private int largestPoolSize;

2.1.1 线程池状态

ThreadPoolExecutor中,线程池有五种状态:

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

五种状态之间的切换如下:

  • RUNNING:线程池被一旦被创建,就处于RUNNING状态,能够接收新任务,以及对已添加的任务进行处理。
  • SHUTDOWN:通过 调用shutdown()方法, 线程池切换到SHUTDOWN状态,不接收新任务,它会等待所有任务执行完毕
  • STOP:通过调用shutdownNow()方法,线程池切换到STOP状态,不接收新任务,并且会去尝试终止正在执行的任务
  • TIDYING:当所有的任务已终止,线程池会变为TIDYING状态,会执行钩子函数terminated(),terminated()在ThreadPoolExecutor类中是空的,可以重写。
  • TERMINATED:线程池处在TIDYING状态时,执行完terminated()之后,线程池彻底终止,就变成TERMINATED状态。

2.1.2 线程池增长策略

  1. 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
  2. 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
  3. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue,并且线程池中的数量小于maximumPoolSize创建新的线程来处理被添加的任务。
  4. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。

即处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。 

2.1.3线程池的拒绝策略

由构造器参数RejectedExecutionHandler  handler表示当拒绝处理任务时的策略,有以下四种取值:

  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionHandler
  2. ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前端的任务,然后重新尝试执行任务
  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

2.1.4 线程池的线程初始化

默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。

在实际中如果需要线程池创建之后立即创建线程,可以通过以下两个方法办到:

  • prestartCoreThread():初始化一个核心线程;
  • prestartAllCoreThreads():初始化所有核心线程

2.1.5 任务缓存队列

workQueue的类型为BlockingQueue<Runnable>,通常可以取下面三种类型:

  • ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小,支持公平锁和非公平锁;
  • LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
  • synchronousQueue:一个不存储元素的阻塞队列,将直接新建一个线程来执行新来的任务,每一个put操作必须等待take操作,否则不能添加元素
  • PriorityBlockingQueue:支持线程优先级排序的无界队列,默认自然序进行排序,也可以自定义实现compareTo()方法来指定元素排序规则

2.1.6 线程池的原理

线程池其实是一个生产者-消费者模型,如下图:

通过调用Executor的execute()方法(或者submit(),submit()也是调用了execute()方法),线程池将任务提交到阻塞队列里。

而另一边,worker线程不断地从队列中取出任务,执行任务的run()方法运行线程。

2.1.7 几种线程池实现

用户可以不自己创建ThreadPoolExecutor

其中,Executors类以静态工厂方法提供了四种线程池的实现:

  • CachedThreadPool:可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • FixedThreadPool :固定线程数量的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • ScheduledThreadPool :固定线程数量的线程池,支持定时及周期性任务执行。
  • SingleThreadExecutor :单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

2.1.8 线程池大小配置

一般需要根据任务的类型来配置线程池大小,假设N为CPU数量:

  • 如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 N+1
  • 如果是IO密集型任务,参考值可以设置为2*N

当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值