深入线程池ThreadPoolExecutor

    我们先从阿里编程手册里面关于并发编程的两点来说明。

6.3:线程资源必须通过线程池创建,不允许在应用中自行显示创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源的不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者"过度切换"的问题。

6.4:线程池不允许使用Executors创建,而是通过ThreadPoolExecutor的方式创建,避免资源资源耗尽。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
2)CacheThreadPool和ScheduledThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的请求,从而导致OOM。

      线程池,是是指一组同构工作线程的资源池。线程池是与工作队列(Work Queue)密切相关的,其中在工作队列中保存了所有等待执行的任务。工作者线程(Worker Thread)的任务很简单:从工作队列中获取一个任务,执行任务,然后返回线程池并等待下一个任务。重用现有线程可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销;当请求到达时,不会等待创建线程而延迟任务的执行,从而提高响应性。适当的调整线程池的大小,1)可以创建足够多的线程以便使处理器保持忙碌状态;2)防止过多线程相互竞争资源使得应用程序内存耗尽或者失败。JUC中可以使用Executors框架来线程池。

 

一、Executors框架

       Executors提供了一个灵活的线程池以及一些有用的默认配置。可以通过调用Executors中的静态工厂方法之一来创建一个线程池:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}


public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

newFixedThreadPool:创建一个固定长度的线程池,每当提交一个任务时就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化。

newCachedThreadPool:将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求时,那么将回收空闲线程,而当需求增加时,则可以添加新的线程,线程池的规模不存在任何限制。

newSingleThreadExecutor:单线程Executor,它创建单个工作者线程来执行任务,如果这个线程异常结束,会创建另一个线程来代替。

newFixedThreadPool:创建一个固定长度的线程池,而且已延迟或定时的方式来执行任务。

Executors的方式非常简单,但是也有一些问题,就像开头所说,很有可能存在OOM,因为任务队列用了无界队列,那么在请求量巨大的时候,会让系统崩溃。可以看到几个静态方法中,底层都是使用的ThreadPoolExecutor,因此,我们在定义线程池的过程中更多的是直接使用它。

 

二、ThreadPoolExecutor

先看一下ThreadPoolExecutor的源码:

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.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

2.1 构造函数的七个参数

1.coolPoolSize:线程池中的常驻核心线程数,即在没有任务执行时线程池的大小

2.maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1

3.keepAliveTime:多余的空闲线程的存活时间。当前线程池数量超过corePoolSIze时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止

4.unit:keepAliveTime的单位

5.workQueue:任务队列,被提交但尚未被执行的任务。ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。基本的任务排队方法有3种:无界队列、有界队列和同步移交。一种稳妥的资源管理策略是使用有界队列,例如ArrayBlockingQueue,有界的LinkedBlockingQueue,PriorityBlockingQueue。有界队列有助于避免资源耗尽的情况发生。

6.threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。

7.handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时如何来拒绝请求执行的runnable的策略。

 

2.2 饱和策略(拒绝策略)

    当有界队列被填满后,饱和策略开始发挥作用。四个拒绝策略:

>AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。

>CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会出异常,而是将某些任务回推到调用者,从而降低新任务的流量。

>DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入到队列中尝试再次提交当前任务。如果工作队列是一个优先级队列,那么"抛弃最旧的"策略将导致抛弃优先级最高的任务,因此最好不要将DiscardOldestPolicy和PriorityBlockingQueue放在一起使用。

>DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

 

 

三、工作原理

3.1 线程池工作原理

3.2 线程池主处理流程

1.在创建了线程池后,等待提交过来的任务请求。

2.当调用execute()方法添加一个请求任务时,线程池会做如下判断:

2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;

2.2 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;

2.3 如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立即运行这个任务;

2.4 如果队列满了且正在运行的线程数量大于或者等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

3.当一个线程完成任务时,它会从队列中取下一个任务来执行。

4.当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池就会判断:如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉。所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

 

四、配置线程池

     如何配置线程池的大小。线程池理想大小取决于被提交的任务类型以及所部署系统的特性。在代码中不会固定线程池的大小,而是应该通过某种配置来提供,或者根据Runtime.availableProcessors来动态计算。

      如果线程池过大,那么大量的线程将在相对很少的CPU和内存资源上发生竞争,这不仅会导致更高的内存使用量,而且还可能耗尽资源;如果线程池过小,那么导致许多空闲处理器无法执行工作,从而降低吞吐率。

      对于计算密集型任务,通常线程池的大小为N(cpu) + 1,可以达到最优的利用率;对于IO密集型操作,线程池应该大一些,线程池的最优大小为:N(threads) = N(cpu) * U(cpu) * (1 + W / C)。U(cpu)是CPU的利用率,0<=U(cpu)<=1。 W/C = ratio of wait time to compute time。U(cpu) = Runtime.getRuntime().availableProcessors()。

 

Author:忆之独秀

Email:leaguenew@qq.com

转载注明出处:https://blog.csdn.net/lavorange/article/details/99706118

 

delphi线程池单元文件uThreadPool.pas,用法如下 type TRecvCommDataWorkItem=class(TWorkItem) public // updatetime,addtime:TDateTime; // orderid,ordertype,urljson,loadcount,savepath:string; url,Filename:string; total,order:Integer; _orderid:string; failedcount:Integer; IFCoverFile:Boolean; // 线程处理请求时触发的事件 procedure DealwithCommRecvData(Sender: TThreadsPool; WorkItem: TWorkItem; aThread: TProcessorThread); // 线程初始化时触发的事件 procedure TProcessorThreadInitializing(Sender: TThreadsPool; aThread:TProcessorThread); // 线程结束时触发的事件 procedure TProcessorThreadFinalizing(Sender: TThreadsPool; aThread:TProcessorThread); //任务队列空时触发的事件 procedure TQueueEmpty(Sender: TThreadsPool; EmptyKind: TEmptyKind); end; 先声明一个类 然后用法 FThreadPool := TThreadsPool.Create(nil); // 创建线程池 FThreadPool.ThreadsMin := 10; // 初始工作线程数 FThreadPool.ThreadsMax := 100; // 最大允许工作线程数 AWorkItem := TRecvCommDataWorkItem.Create; ISAllOverLoad:=False; AWorkItem.url:=urljson; AWorkItem.order:=i; AWorkItem.total:=JA.Count; AWorkItem.Filename:=savefilepath; AWorkItem._orderid:=orderid; AWorkItem.IFCoverFile:=IFCoverFile; FThreadPool.AddRequest(AWorkItem,True); // 向线程池分配一个任务 FThreadPool.OnProcessRequest := AWorkItem.DealwithCommRecvData; FThreadPool.OnThreadInitializing := AWorkItem.TProcessorThreadInitializing; FThreadPool.OnThreadFinalizing := AWorkItem.TProcessorThreadFinalizing; FThreadPool.OnQueueEmpty := AWorkItem.TQueueEmpty; 仔细看下线程池单元的函数说明轻松搞定。 procedure TRecvCommDataWorkItem.TQueueEmpty(Sender: TThreadsPool; EmptyKind: TEmptyKind); begin if EmptyKind=ekProcessingFinished then begin try if Assigned(geturl) then //存在的bug 如果下载文件存在的不行 begin //Sleep(200); //激活线程可能会发生在 休眠之前!! ISAl
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值