线程池概述

基本概念:

由于线程的生命周期中包括创建、就绪、运行、阻塞、销毁阶段,当我们待处理的任务数目较小时,我们可以自己创建几个线程来处理相应的任务,但当有大量的任务时,由于创建、销毁线程需要很大的开销,运用线程池这些问题就大大的缓解了。

使用线程池的优势

①降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;

②提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;

③方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;

④更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。

创建线程的方式:

1.jdk提供的4种默认的线程池的实现,都使用Executors来创建的。

这四种线程池分别是newFixedThreadPool,newCachedThreadPool,newScheduledThreadPool和newSingleThreadExecutor。

  • newFixedThreadPool (固定数目线程的线程池)
  • newCachedThreadPool(可缓存线程的线程池)
  • newSingleThreadExecutor(单线程的线程池)
  • newScheduledThreadPool(定时及周期执行的线程池)

缺点:阿里巴巴的开发手册有说到线程池不允许使用Executors去创建,在资源有限的情况下容易引起oom问题,而是通过ThreadPoolExecutor的方式。例如newFixedThreadPool线程池,底层的队列用的是linkedBlockingQueue,是一个无界队列,因此往队列中可以插入无限多的任务,在资源有限的时候容易引起OOM异常,同时因为无界队列,maximumPoolSize和keepAliveTime参数将无效。newCachedThreadPool,阻塞队列是SynchronousQueue,当提交任务的速度大于处理任务的速度时,每次提交一个任务,就必然会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。

2.自定义线程池,通过调用 ThreadPoolExecutor 的构造方法实现的。

  • corePoolSize: 线程池中的核心线程数,默认情况下,核心线程一直存活在线程池中,即便他们在线程池中处于闲置状态。
  • maximumPoolSize: 线程池中所容纳的最大线程数,如果活动的线程达到这个数值以后,后续的新任务将会被阻塞。包含核心线程数+非核心线程数。如果没有最大数量,当创建的线程数量达到了某个极限值,内存肯定就爆掉了。
  • keepAliveTime: 非核心线程闲置时的超时时长,对于非核心线程,闲置时间超过这个时间,非核心线程就会被回收。默认值是60秒。
  • unit: 

    用于指定keepAliveTime参数的时间单位。他是一个枚举,可以使用的单位有天(TimeUnit.DAYS),小时(TimeUnit.HOURS),分钟(TimeUnit.MINUTES),毫秒(TimeUnit.MILLISECONDS),微秒(TimeUnit.MICROSECONDS, 千分之一毫秒)和毫微秒(TimeUnit.NANOSECONDS, 千分之一微秒);

  • workQueue: 线程池中保存等待执行的任务的阻塞队列。

       ArrayBlockingQueue:基于数组实现的有界的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。

      LinkedBlockingQueue:基于链表实现的阻塞队列,该队列按照FIFO(先进先出)原则对队列中的元素进行排序。

     SynchronousQueue:内部没有任何容量的阻塞队列。在它内部没有任何的缓存空间。对于SynchronousQueue中的数据元素只有当我们试着取走的时候才可能存在。

     PriorityBlockingQueue:具有优先级的无限阻塞队列。

    我们还能够通过实现BlockingQueue接口来自定义我们所需要的阻塞队列。

  • threadFactory: 线程工厂,为线程池提供新线程的创建。ThreadFactory是一个接口,里面只有一个newThread方法。 默认为DefaultThreadFactory类。

  • handler: 线城池的饱和策略事件,主要有四种类型。

当任务队列已满并且线程池中的活动线程已经达到所限定的最大值或者是无法成功执行任务,这时候ThreadPoolExecutor会调用RejectedExecutionHandler中的rejectedExecution方法。在ThreadPoolExecutor中有四个内部类实现了RejectedExecutionHandler接口。在线程池中它默认是AbortPolicy,在无法处理新任务时抛出RejectedExecutionException异常。

下面是在ThreadPoolExecutor中提供的四个可选值。

CallerRunsPolicy:只用调用者所在线程来运行任务。

AbortPolicy:直接抛出RejectedExecutionException异常。

DiscardPolicy:丢弃掉该任务,不进行处理

DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。

我们也可以通过实现RejectedExecutionHandler接口来自定义我们自己的handler。如记录日志或持久化不能处理的任务。

自定义线程池的拒绝策略:

jdk内置的四种拒绝策略(都在ThreadPoolExecutor.java里面)代码都很简洁易懂。

我们只要继承接口都可以根据自己需要自定义拒绝策略。下面看两个例子。

一是netty自己实现的线程池里面私有的一个拒绝策略。单独启动一个新的临时线程来执行任务。

    private static final class NewThreadRunsPolicy implements RejectedExecutionHandler {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            try {
                final Thread t = new Thread(r, "Temporary task executor");
                t.start();
            } catch (Throwable e) {
                throw new RejectedExecutionException(
                        "Failed to start a new thread", e);
            }
        }
    }

另外一个是dubbo的一个例子,它直接继承的 AbortPolicy ,加强了日志输出,并且输出dump文件

public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        String msg = String.format("Thread pool is EXHAUSTED!" +
                        " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d)," +
                        " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
                threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(),
                e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
                url.getProtocol(), url.getIp(), url.getPort());
        logger.warn(msg);
        dumpJStack();
        throw new RejectedExecutionException(msg);
    }
}


线程池的使用:

可以通过execute和submit两种方式来向线程池提交一个任务。

execute:使用execute来提交任务时,由于execute方法没有返回值,所以说我们也就无法判定任务是否被线程池执行成功。

submit: 当我们使用submit来提交任务时,它会返回一个future,我们就可以通过这个future来判断任务是否执行成功,还可以通过future的get方法来获取返回值。

线程池的关闭

线程池提供了两个关闭方法,shutdownNowshuwdown方法。

shutdownNow方法的解释是:线程池拒接收新提交的任务,同时立马关闭线程池,线程池里的任务不再执行。将线程池状态修改为STOP,然后调用线程池里的所有线程的interrupt方法

shutdown方法的解释是:线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。将线程池的状态修改为SHUTDOWN状态,然后调用interruptIdleWorkers方法,来中断空闲的线程。

注意:调用完shutdownNow和shuwdown方法后,并不代表线程池已经完成关闭操作,它只是异步的通知线程池进行关闭处理。如果要同步等待线程池彻底关闭后才继续往下执行,需要调用awaitTermination方法进行同步等待。

线程池的生命周期:

ThreadPoolExecutor的源码使用位移运算来表示线程池状态,5个Integer表示的状态属性里,右29位表示工作线程数,左3位表示线程池状态

五种状态的十进制值按从小到大依次排序为:

RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED

这样可以通过比较值的大小来确定线程池的状态

RUNNING表示线程池能接受新任务。线程池在构造前(new操作)是初始状态,一旦构造完成线程池就进入了执行状态。

SHUTDOWN:关机,停工。表示此状态不再接受新任务,但可以继续执行队列中的任务,包括那些进入队列还没有开始的任务。

STOP 表示此状态全面拒绝,并中断正在处理的任务。shodownNow()后会进入这个状态。

TIDYING:整理,收拾。表示所有任务已经被终止。

TERMINATED:终止,结束。表示此状态已清理完现场。



线程池的处理流程:

  • 提交任务后,线程池先判断线程数是否达到了核心线程数(corePoolSize)。如果未达到线程数,则创建核心线程处理任务;否则,就执行下一步;
  • 接着线程池判断任务队列是否满了。如果没满,则将任务添加到任务队列中;否则,执行下一步;
  • 接着因为任务队列满了,线程池就判断线程数是否达到了最大线程数。如果未达到,则创建非核心线程处理任务;否则,就执行饱和策略,默认会抛出RejectedExecutionException异常。

如何配置线程池

CPU密集型任务
尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

IO密集型任务
可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

混合型任务
可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。 只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。
因为如果划分之后两个任务执行时间有数据级的差距,那么拆分没有意义。
因为先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。

参考链接:

该内容暂无法显示 - 知乎

Java线程池详解 - 简书

Java 线程池详解 - 简书

Java线程池详解 - 简书

https://www.hollischuang.com/archives/2888

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值