线程池

线程池作为一种线程资源管理机制,引入的出发点是将任务的执行和提交分离。任务的执行一般不会在当前线程(调用者线程)中进行,而是委托为一个服务,例如线程池。利用Executor框架构造线程池,实现对任务的执行策略的管理,在ExecutorService中提供了对线程池本身的管理。

线程作为一个大对象,构造对象和回收对象都是需要一定资源消耗,线程池可以实现对线程对象的重复利用,以此削减执行任务需要的时间,使用阻塞队列来存储任务相比较于使用线程对象节省了内存。

利用原始的Executor接口,及其实现类ThreadPoolExecutor构造线程池

示例:

<span style="font-family:FangSong_GB2312;font-size:18px;">public class t{
	public static void main(String[] args) {
		int corePoolSize=1;//核心线程数
		int maximumPoolSize=2;//线程池最多运行线程数
		long keepAliveTime=1L;//空闲生存时间量值
		TimeUnit unit=TimeUnit.SECONDS;//空闲生存时间单位
		BlockingQueue<Runnable> workQueue=new LinkedBlockingQueue<Runnable>(10);//任务队列
		ThreadFactory threadFactory=new ThreadFactory(){
			public Thread newThread(Runnable r){
				return new Thread(r);
			}
		};//线程工厂
		RejectedExecutionHandler handler=new ThreadPoolExecutor.AbortPolicy();//拒绝策略
		Executor exePool=new ThreadPoolExecutor(corePoolSize,
                              maximumPoolSize,
                              keepAliveTime,
                              unit,
                              workQueue,
                              threadFactory,
                              handler);

		testRunnable r1=new testRunnable();//任务对象
		exePool.execute(r1);
		exePool.execute(r1);
	}
}
class testRunnable implements Runnable{//测试任务
	public void run(){
		System.out.println("thread number:"+Thread.currentThread().getId()+" come in");
		try{
			Thread.sleep(3000);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		System.out.println("thread number:"+Thread.currentThread().getId()+" come out");
	}
}</span>
参数介绍:

<span style="font-family:FangSong_GB2312;font-size:18px;">int corePoolSize   线程池中运行的线程数
int maximumPoolSize  线程池中线程数能达到的最大值
long keepAliveTime  线程数大于corePoolSize值后,线程任务执行完毕被回收之前的等待时间,也叫生存时间
TimeUnit unit   生存时间单位
BlockingQueue<Runnable> workQueue	等待执行的任务队列
ThreadFactory threadFactory	线程工厂,ThreadFactory接口中只有一个newThread方法,后面的实现类会添加线程描述信息
RejectedExecutionHandler handler	拒绝提交的任务时执行的策略,任务队列已满或者线程池已经关闭,
		默认的AbortPolicy抛出RejectedExecutionException</span>

在提交任务时,如果线程池中线程数没有达到corePoolSize,则直接创建线程执行,否则将任务存入任务队列,队列存满则判断线程数是否达到maximumPoolSize,没有达到则创建线程(不使用已有的线程),否则执行拒绝策略。

拒绝策略四种类型

AbortPolicy:默认使用的策略,抛出RejectedExecutionException,线程池作为执行线程,在任务执行过程中遇到异常应该交给调用者线程来处理,毕竟自己只是个打工的,出现情况要及时反映给决策者,自己最多做一些清零工作,然后保存异常呈上去。

DiscardPolicy、DiscardOldestPolicy:任务无法提交给服务完成,就直接丢掉了,而且连个招呼都不打,不同在于DiscardPolicy抛弃待提交的任务,DiscardOldestPolicy把等待时间最久的扔掉,并且在这种策略下,Future的get方法(没有等待时间参数的get)也不会抛出异常,因为任务是直接被discard,而不是cancel。

CallerRunsPolicy:队列满了则再提交的任务交由调用者线程来执行,不过此策略下,如果线程池已关闭,则再提交任务不会执行此策略,而且调用者线程被占用,可能使得服务器在应用程序层面一段时间内无法接收新的请求。

ExecutorService、ScheduledExecutorService和Executors

使用原始的Executor和ThreadPoolExecutor构造线程池比较麻烦,在Executors工厂类中提供了直接构造线程池的方法。常用的几种线程池如下:

<span style="font-family:FangSong_GB2312;font-size:18px;">public static ExecutorService newFixedThreadPool(int nThreads)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ExecutorService newCachedThreadPool()
public static ExecutorService newSingleThreadExecutor()</span>
Executors中newFixedThreadPool的实现方法为

<span style="font-family:FangSong_GB2312;font-size:18px;">public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }</span>
ThreadPoolExecutor中的实现为

<span style="font-family:FangSong_GB2312;font-size:18px;">public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }</span>

最终调用的构造方法为

<span style="font-family:FangSong_GB2312;font-size:18px;">public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();//检查基本类型的参数有效性
        if (workQueue == null || threadFactory == null || handler == null)//检查参数引用对象是否构造
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }</span>

所以包括其他几种类型的线程池工厂方法,都是对七个参数的ThreadPoolExecutor构造方法的包装,以较为简单的方式完成具有不同特点的线程池的构造。

常用线程池类型

newFixedThreadPool、newSingleThreadExecutor:这两种线程池具有固定大小的线程数,使用的都是无界阻塞队列LinkedBlockingQueue,也可以设置大小,虽然存储为任务对象,而非线程对象,但是如果某个时间段存在任务快速到达,并超过线程池处理任务速度,可能存在内存消耗过多问题。其中newSingleThreadExecutor作为单线程线程池,实现了任务的顺序访问,并且保证了任务不会并发的执行,因此程序结构上对线程安全的要求可以放宽,在线程因为异常而取消时,可以自动重新生成一个新的线程。

newCachedThreadPool:具有缓冲功能的线程池,线程数量限制为(0~Integer.MAX_VALUE),即大小不限,生存时间为60秒,保证线程数随实际需求而变动,使用阻塞队列为SynchronousQueue。该队列实现的是一个直接移交任务的功能,在放置任务的时候,必须有取任务的操作与之对应,否则将直接创建线程对象执行任务或执行拒绝策略。
newScheduledThreadPool:常用方法为

<span style="font-family:FangSong_GB2312;font-size:18px;">ScheduledFuture<?> schedule(Runnable command,
                            long delay,
                            TimeUnit unit)
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                       long initialDelay,
                                       long period,
                                       TimeUnit unit)</span>
延迟某个时间后执行和延迟时间后以固定时间间隔执行,提供循环执行功能。
ExecutorService提供了对线程池的管理

常用函数有

shutdown函数,不再接受新的线程,等待当前任务以及队列中的任务执行完毕后关闭线程池

shutdownNow函数,不再接受新的线程并且尝试去停止正在执行线程

总结

设计线程池主要关注以下几个方面:

线程池大小,避免两个极端,线程池中线程数量太少时,遇到同时执行的时间较长的任务会导致线程池响应性下降,并且当线程之间存在依赖关系时,可能会发生死锁,导致线程饥饿现象。线程数太多会消耗太多的内存,并且在CPU资源上竞争较严重。

阻塞队列的选择,选择无界的阻塞队列时需要选择合理生存时间,根据任务提交情况,不会导致频繁的线程对象创建和销毁,又不会在线程池中存在过多空闲线程。

因为SynchronousQueue的直接移交任务特性,可以直接交给空闲线程或者创建线程对象执行,所以搭配选择较大线程数量的线程池避免拒绝任务,可以提高执行效率。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值