二、多线程(2)线程池ThreadPool

目录

一、概念

二、线程池创建方式

1.ThreadPoolExecutor

① int corePoolSize

②int maximumPoolSize

③long keepAliveTime

④BlockingQueue workQueue

⑤RejectedExecutionHandler handler

2.Executors工具类

①可缓存线程池

②定长线程池

③周期执行线程池

④单例线程池

三、执行策略

四、 线程池合理配置

1.cpu密集型任务

2.IO密集型任务

五、几个核心类的继承关系


一、概念

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

好处
1)降低资源消耗,重复利用已经创建好的线程,降低线程创建和销毁造成的消耗

2)提高响应速度,当任务到达时,任务可以不需要等到线程创建就可以立即执行

3)提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。

二、线程池创建方式

1.ThreadPoolExecutor

构造函数

 

序号名称类型含义
1corePoolSizeint核心线程池大小
2maximumPoolSizeint最大线程池大小
3keepAliveTimelong线程最大空闲时间
4unitTimeUnit时间单位
5workQueueBlockingQueue<Runnable>线程等待队列
6threadFactoryThreadFactory线程创建工厂
7handlerRejectedExecutionHandler拒绝策略

① int corePoolSize

指该线程池中核心线程数最大值

核心线程:线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程。核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。

如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉。

②int maximumPoolSize

指该线程池中线程总数最大值。

线程总数 = 核心线程数 + 非核心线程数。

③long keepAliveTime

指该线程池中非核心线程闲置超时时长

一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。

④BlockingQueue workQueue

指该线程池中的任务队列。

当所有的核心线程都忙碌时,新添加的任务会被添加到这个队列中等待处理;如果队列满了,则新建非核心线程执行任务。

常用的workQueue类型:SynchronousQueue、LinkedBlockingQueue、ArrayBlockingQueue、DelayQueue。

1)   SynchronousQueue

这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在忙碌,那就新建一个线程来处理这个任务。此队列通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。

此时需要注意线程数目与maximumPoolSize的关系,以及线程数目过多而消耗的服务器资源。

2)   LinkedBlockingQueue

这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。

由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize。

3)   ArrayBlockingQueue

可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误。

4)   DelayQueue

队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务

RejectedExecutionHandler handler

线程池无法处理任务时的丢弃策略。

1)   CallerRunsPolicy

拒绝这个任务,不在ThreadPoolExecutor线程池中的线程中运行,而是调用当前线程池的所在的线程去执行被拒绝的任务。

2)   AbortPolicy

ThreadPoolExecutor默认的拒绝策略,直接抛出异常。

3)   DiscardPolicy

线程池默默丢弃这个被拒绝的任务,不会抛出异常。

4)   DiscardOldestPolicy

会抛弃任务队列中最旧的任务(最先加入队列的任务),再把这个新任务添加到队列中去。

5)   总结

实际上查阅这四种ThreadPoolExecutor线程池自带的拒绝处理器实现,您可以发现CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy处理器针对被拒绝的任务并不是一个很好的处理方式。

CallerRunsPolicy在非线程池以外直接调用任务的run方法,可能会造成线程安全上的问题。

DiscardPolicy默默的忽略掉被拒绝任务,也没有输出日志或者提示,开发人员不会知道线程池的处理过程出现了错误;

DiscardOldestPolicy大部分情况下,默默地抛弃一部分任务都是一件很危险的事情。

通常使用AbortPolicy是最好的,因为抛弃任务时,开发者可以通过日志发现这个问题,并着手处理。

2.Executors工具类

Executors工具类提供了四种常用线程池的创建:

①可缓存线程池

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

ExecutorService mCachedThreadPool = Executors.newCachedThreadPool();

或者:

 public static ExecutorService newCachedThreadPool() {

        return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new                 
                                                          SynchronousQueue<Runnable>());

    }

它是一种线程数量不定的线程池,它只有非核心线程,并且其最大线程数为Integer.MAX_VALUE。由于Integer.MAX_VALUE是一个很大的数,实际上就相当于最大线程数可以任意大。当线程池中的线程都是处于活动状态时,线程池会创建新的线程来处理新任务,否则就会利用空闲的线程来处理新任务。线程池中的空闲线程都有超时机制,这个超时长为60秒,超过60秒闲置线程就会被回收。和FixedThreadPool不同的是,CachedThreadPool的任务队列其实相当于一个空集合,这将导致任何任务都会立即被执行,因为在这种场景下SynchronousQueue是无法插入任务的。SynchronousQueue是一个非常特殊的队列,在很多情况下可以把它简单理解为一个无法存储元素的队列,由于它在实际中较少使用,这里就不探讨了。从CachedThreadPool的特性来看,这类线程池比较适合执行大量的耗时较少的任务。当整个线程池都处于闲置状态时,线程池中的线程都会超时而被终止,这个时候CachedThreadPool之中实际上是没有任何线程的,它几乎是不占用任何系统资源的

②定长线程池

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

ExecutorService mFixedThreadPool= Executors.newFixedThreadPool(int nThreads);

或者:

public static ExecutorService newFixedThreadPool(int nThreads) { 

return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new     
                                                 LinkedBlockingQueue<Runnable>());

}

它是一种线程数量固定的线程池,当线程池处于空闲状态时,它们并不会被回收,除非线程池被关闭了。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。由于FixedThreadPool只有核心线程线程并且这些核心线程不会被回收,这意味着它能过更加快速的相应外界的请求。newFixedThreadPool方法的实现如下,可以发现FixedThreadPool中只有核心线程并且这些核心线程没有超时机制,另外任务队列也是没有大小限制的。

③周期执行线程池

创建一个定长线程池,支持定时及周期性任务执行。

ExecutorService  scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);

或者:

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
              new DelayedWorkQueue());
    }

它的核心线程数量时固定的,而非核心线程数是没有限制的,并且当非核心线程闲置是会被立即回收。ScheduledThreadPool这类线程主要用于执行定时任务和具有固定周期的重复任务

④单例线程池

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

 ExecutorService mSingleThreadPool = Executors.newSingleThreadPool();

或者:

public static ExecutorService newSingleThreadExecutor() { 

return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, 
                       TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); 

}

这类线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有的外界任务到一个线程中,这使得这些任务之间不需要处理线程同步的问题。

三、执行策略

通常当一个任务被添加进线程池时,总体的执行策略如下,不过具体会根据核心属性定义的值会有少许变动。

线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务

线程数量达到了corePools,则将任务移入队列等待。

队列已满,新建线程(非核心线程)执行任务

队列已满,总线程数又达到了maximumPoolSize,就会由(RejectedExecutionHandler)抛出异常

四、 线程池合理配置

1.cpu密集型任务

cpu执行速度非常快,往往切换线程上下文环境所耗费的时间比执行代码花费的时间更长

因此:线程数应该尽量保持和CPU核数一致

2.IO密集型任务

IO密集型任务的主要性能瓶颈在于等待IO结果,当遇到任务中存在从DB中读取数据,从缓存中读取数据时,就可以认为是IO密集型任务

假设:an表示系统平均每秒收到的请求数;mn表示系统每秒收到的峰值请求数;at表示每个任务执行的平均时间;more表示预留的buffer;n表示线程池个数;那么线程池可以参考一下公式配置:

核心线程数 = an * at /n * (1+more%)

最大线程数 = mn * at /n * (1+more%)

五、几个核心类的继承关系

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值