线程池

使用线程池的目的

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

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

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

线程池参数

public ThreadPoolExecutor(int corePoolSize,
                         int maximumPoolSize,
                         long keepAliveTime,
                         TimeUnit unit,
                         BlockingQueue<Runnable> workQueue,
                         ThreadFactory threadFactory,
                         RejectedExecutionHandler handler) {
}

int corePoolSize: 核心线程数最大值

int maximumPoolSize:该线程池中线程总数最大值

long keepAliveTime:非核心线程闲置超时时长

TimeUnit unit:时间单位(枚举:纳秒,微秒,毫秒,秒,分,时,天)

BlockingQueue workQueue: 当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务

ThreadFactory threadFactory: 创建线程的方式

RejectedExecutionHandler handler:任务队列和最大线程都满了之后的拒绝策略

线程池原理

  1. 提交一个任务,如果线程池存活线程数小于核心线程数corePoolSize,直接创建线程处理新来的任务。

  2. 如果核心线程已满,新来的任务放入任务队列workQueue等待执行。

  3. 核心线程与任务队列都满了以后,判断当前线程数是否达到最大线程数maximumPoolSize,没达到就创建非核心线程执行任务。

  4. 当前线程数达到了最大线程数,采用拒绝策略handler处理。

阻塞队列

ArrayBlockingQueue: 基于数组的阻塞队列实现。生产者放入数据和消费者获取数据,共用同一个锁对象。默认采用非公平锁。

LinkedBlockingQueue:基于链表的阻塞队列。生产者端和消费者端分别采用了独立的锁来控制数据同步,并发性能较好。需要注意的是,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE)。

PriorityBlockingQueue:基于优先级的阻塞无界队列(优先级的判断通过构造函数传入的Compator对象来决定,但不保证同优先级元素顺序)。不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。内部控制线程同步的锁采用的是公平锁。

SynchronousQueue: 无缓冲的等待队列。每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都要等待另一个线程的插入操作。由于队列没有容量,所以不能调用peek操作(返回队列头元素)。

DelayQueue: 支持延时获取元素的无界阻塞队列。队列中每个元素必须实现Delayed接口。插入数据的操作(生产者)永远不会被阻塞,只有获取数据的操作(消费者)才会被阻塞。

拒绝策略

AbortPolicy:java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute需要try catch,否则程序会直接退出。

DiscardPolicy:直接抛弃,任务不执行,空方法。

DiscardOldestPolicy:从队列里面抛弃head的一个任务,并再次execute 此任务。

CallerRunsPolicy:在调用execute的线程里面执行此任务,会阻塞入口。

线程池分类

newFixedThreadPool

固定数目线程的线程池

适用于处理CPU密集型任务,长期的任务。

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

newSingleThreadExecutor

适用于需要保证顺序地执行各个任务 。 它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

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

newCachedThreadPool

可缓存的线程池

适合并发执行大量短期的小任务

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

newScheduledThreadPool

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

和其他线程池最大的区别是使用的阻塞队列是 DelayedWorkQueue,而且多了两个定时执行的方法scheduleAtFixedRate和scheduleWithFixedDelay

  • scheduleAtFixedRate() :按某种速率周期执行

  • scheduleWithFixedDelay():在某个延迟后执行

两种方法的内部实现都是创建了一个ScheduledFutureTask对象封装了任务的延迟执行时间及执行周期,并调用decorateTask()方法转成RunnableScheduledFuture对象,然后添加到延迟队列中。

DelayQueue:中封装了一个优先级队列,这个队列会对队列中的ScheduledFutureTask 进行排序,两个任务的执行 time 不同时,time 小的先执行;否则比较添加到队列中的ScheduledFutureTask的顺序号 sequenceNumber ,先提交的先执行。 具体执行步骤: 线程从 DelayQueue 中获取 time 大于等于当前时间的 ScheduledFutureTask DelayQueue.take() 执行完后修改这个 task 的 time 为下次被执行的时间 然后再把这个 task 放回队列中 DelayQueue.add()

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { 
   return new ScheduledThreadPoolExecutor(corePoolSize);}
public ScheduledThreadPoolExecutor(int corePoolSize) {
   super(
       corePoolSize,
       Integer.MAX_VALUE,
       0,
       NANOSECONDS,
       new DelayedWorkQueue());
}
public static ScheduledExecutorService newScheduledThreadPool(
   int corePoolSize,ThreadFactory threadFactory) {
       return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(
   int corePoolSize,ThreadFactory threadFactory) {
       super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
             new DelayedWorkQueue(), threadFactory);
}

newWorkStealingPool

适合使用在很耗时的操作,但是newWorkStealingPool不是ThreadPoolExecutor的扩展,它是新的线程池类ForkJoinPool的扩展,但是都是在统一的一个Executors类中实现,由于能够合理的使用CPU进行对任务操作(并行操作),所以适合使用在很耗时的任务中 。

public static ExecutorService newWorkStealingPool(int parallelism) {    
   return new ForkJoinPool(
       parallelism,        
       ForkJoinPool.defaultForkJoinWorkerThreadFactory,
       null,
       true);
}

线程池状态

  1. RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;

  2. SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在执行过程中也会调用shutdown()方法进入该状态);

  3. STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态;

  4. TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。

  5. TERMINATED

    :在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。

    进入TERMINATED的条件如下:

    • 线程池不是RUNNING状态;

    • 线程池状态不是TIDYING状态或TERMINATED状态;

    • 如果线程池状态是SHUTDOWN并且workerQueue为空;

    • workerCount为0;

    • 设置TIDYING状态成功。

合理地配置线程池

要想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。

任务的性质:CPU密集型任务、IO密集型任务和混合型任务

任务的优先级:高中低

任务的执行时间:长中短

任务的依赖性:是否依赖其他系统资源

  • CPU密集型任务应配置尽可能小的线程,如配置NCPU+1个线程的线程池。

  • IO密集型任务线程并不是一直在执行任务 ,则应配置尽可能多的线程,如2*NCPU 。

  • 对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。

可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

  • 优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理,它可以让优先级高的任务先执行。

  • 若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。

  • 最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池? (1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换。 (2)并发不高、任务执行时间长的业务要区分开看:   a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以适当加大线程池中的线程数目,让CPU处理更多的业务。   b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样,线程池中的线程数设置得少一些,减少线程上下文的切换。 (3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

线程池的监控

通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用

  • getTaskCount:线程池已经执行的和未执行的任务总数;

  • getCompletedTaskCount:线程池已完成的任务数量,该值小于等于taskCount;

  • getLargestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过,也就是达到了maximumPoolSize;

  • getPoolSize:线程池当前的线程数量;

  • getActiveCount:当前线程池中正在执行任务的线程数量。

通过这些方法,可以对线程池进行监控,在ThreadPoolExecutor类中提供了几个空方法,如beforeExecute方法,afterExecute方法和terminated方法,可以扩展这些方法在执行前或执行后增加一些新的操作,例如统计线程池的执行任务的时间等,可以继承自ThreadPoolExecutor来进行扩展。

posted on 2021-09-13 22:43  玖伍贰柒9527  阅读(45)  评论(0)    收藏  举报

导航