java线程池

Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池,所以我们就要认识并弄懂线程池,以便于更好的为我们业务场景服务。

1 线程池的好处

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;
  • 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌

2 线程池的工作流程

  1. 当提交一个新任务到线程池时,线程池判断corePoolSize线程池是否都在执行任务,如果有空闲线程,则创建一个新的工作线程来执行任务,直到当前线程数等于corePoolSize;
  2. 如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行;
  3. 如果阻塞队列满了,那就创建新的线程执行当前任务,直到线程池中的线程数达到maxPoolSize,这时再有任务来,由饱和策略来处理提交的任务

3 线程池的参数

下面是ThreadPoolExecutor类的构造方法传参数

public ThreadPoolExecutor(int corePoolSize, #核心线程数
                              int maximumPoolSize,   #最大线程数
                              long keepAliveTime, #达到最大线程数数时候,线程池的工作线程空闲后,保持存活的时间
                              TimeUnit unit,     #keepAliveTime单位
                              BlockingQueue<Runnable> workQueue #阻塞队列
                              RejectedExecutionHandler handler  #饱和策略

) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
new ThreadPoolExecutor(6 ,12, 5L, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(10),new ThreadPoolExecutor.CallerRunsPolicy());

比如corePoolSize为6, maximumPoolSize为,12
keepAliveTime为5秒,队列长度为10;提交任务数达到核心线程数6时候,新来的任务就会被放入LinkedBlockingQueue阻塞队列,当队列任务数达到10个时候,就会创建新线程执行任务,直到达到maximumPoolSize数量12;如果还有新来的任务,由策略来处理提交的任务;如果没有,线程池空闲时候,超过5秒,创建的maximumPoolSize,就会被销毁。

4 阻塞队列

阻塞队列BlockingQueue接口,从jdk1.5开始,有四个实现类,jdk8亦是如此
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue,静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
SynchronousQueue一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个列
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

5 四种饱和策略

RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常,查看源码,从jdk5开始RejectedExecutionHandler接口有四个实现类,jdk8亦是如此。

AbortPolicy:不处理,直接抛出异常。
CallerRunsPolicy:只用调用者所在线程来运行任务,即提交任务的线程。
DiscardOldestPolicy:LRU策略,丢弃队列里最近最久不使用的一个任务,并执行当前任务。
DiscardPolicy:不处理,丢弃掉,不抛出异常。

6 向线程池提交任务

可以使用两个方法向线程池提交任务,分别为execute()和submit()方法
1、execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
2、submit()方法用于提交需要返回值的任务。
线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit),在指定的时间内会等待任务执行,超时则抛出超时异常,等待时候会阻塞当前线程

7 关闭线程池

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true

因此,判断线程池所有线程是否执行完成,可以这样写:

while(true){//死循环
    if(threadPool.isTerminated()) {
            //执行自己的操作
            break;//true停止
    }
    Thread.sleep(500);//休眠500继续循环
}

shutdown,只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程,等待执行任务的线程完成。
shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

8 线程池的五种状态

1、RUNNING
(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(2) 状态切换:线程池的初始化状态是RUNNING。线程池被一旦被创建,就处于RUNNING状态,且线程池中的任务数为0

2、 SHUTDOWN
(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

3、STOP
(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。

4、TIDYING
(1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。

5、 TERMINATED
(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

9 调整线程池的大小

ThreadPoolExecutor提供了setter方法,因此创建线程池之后,也可以动态调整线程池大小

10 监控线程池

可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性,获取线程池任务状况

taskCount:线程池需要执行的任务数量。

completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
largestPoolSize:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。
getPoolSize:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销 毁,所以这个大小只增不减。
getActiveCount:获取活动的线程数。

也可以通过扩展线程池进行监控。可以通过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。

例如,监控任务的平均执行时间、最大执行时间和最小执行时间等,这几个方法在线程池里是空方法

11 使用线程池

  • JDK 5.0起提供了线程池相关API:ExecutorService 和 Excecutors
  • ExecutorService:真正的线程池接口。常见子类 ThreadPoolExecutor
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • <T>  Future<T>   submit(Callable<T> task) : 执行任务,有返回值,一般用来来执行Callable
    • void shutdown():关闭线程池
  • Executors:工具类、线程池的工厂类,用来创建并返回不同类型的线程池
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值