Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。

1.线程池的核心参数有哪些

1.CorePoolSize:核心线程数
2.QueueCapacity:任务队列容量(阻塞队列)
当核心线程数达到最大时,新任务会放在队列汇总排队等待执行。
3.MaxPoolSize:最大线程数
当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务。
当线程数=maxpoolSize,且任务队列已满时,线程池会拒绝处理而抛出异常。
4.KeepAliveTime:线程空闲时间
如果线程空闲时间达到keepAliveTime,线程会退出。直到线程数量=corePoolSize,如果allowCoreThreadTimeout=true,则会直到线程数量=0
5.AllowCoreThreadTimeOut:允许核心线程超时
6.RejectedExecutionHandler:任务拒绝处理器

2.线程池的好处

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是要做到合理利用线程池,必须对其实现原理了如指掌。

2.线程池的实现原理(当向线程池提交任务后,线程池如何处理)

Java 线程池选择题 java 线程池 面试题_Java 线程池选择题


向线程池提交任务,分别为execute()和submit()。

execute()提交不需要返回值的任务。

submit()提交需要返回值的任务,线程池会返回一个future对象。可以通过Future.get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(longe timeout,TimeUnit unit)方法会阻塞当前线程一段时间后立即返回,这时候有可能任务还没有执行完。

流程如下:

(1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的线程来执行任务(全局锁)。如果核心线程池里的线程都在执行任务,则进入下个流程。

(2)线程池判断工作队列是否已经满。如果工作队列还没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

(3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务(全局锁)。如果满了,则交给饱和策略来处理这个问题。

3.饱和策略(线程池的队列满了怎么处理)

线程池的所有线程都处于工作状态(线程池满了),有一下饱和策略。
Abort策略、CallerRuns策略、Discard策略、DiscardOlds策略
1.Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectExecutionException。
2.CallerRuns策略:调节机制,既不抛弃任务也不抛弃异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用exector的线程中运行新的任务。
3.Discard策略:新提交的任务被抛弃
4.DiscardOldest策略:丢弃的是队头任务,然后尝试提交新的任务,不适合工作队列为优先队列的场景。

4.如何创建线程池

《阿里巴巴Java开发手册》中强制线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的通信更加明确线程池的运行规则,规避资源耗尽的风险。
Executor是返回线程池对象的弊端如下:

  • FixedThreadPool和SingleThreadExecutor:允许请求的队列长度为Integer.MAX_VALUE。可能堆积大量请求从而导致OOM
  • CacheThreadPool和ScheduleThreadPool:允许创建的线程数量为Integer.MAX_VALUE,可能会创建大量线程,从而导致OOM

方式一:通过构造方法实现

Java 线程池选择题 java 线程池 面试题_线程池_02


ThreadPoolExecutor 的3个重要参数:

  • corePoolSize:核心线程数定义了最小可同时运行的线程数量。
  • maximumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
  • workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。

其他参数

  • keepAliveTime:当线程池中的线程数量大于corePoolSize的时候,如果这时候没有新的任务提交,核心线程外的线程不会被立即销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被回收销毁。
  • unit:keepAliveTime参数的时间单位。
  • threadFactory:executor创建新线程时候会用到
  • handler:饱和策略
    饱和策略
  • AbortPolicy
  • CallerRunsPolicy
  • DiscardPolicy
  • DiscardOldestPolicy

方式二:通过Executor框架的工具类Executor是来实现

可以创建三种类型的ThreadPoolExecutor:

  • FixedThreadPool:该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在队列中的任务。
  • SingleThreadExecutor:方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
  • CachedThreadPool:该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。

有哪些线程池

通过Executor框架的工具类Executors,可以创建三种类型的ThreadPoolExecutor。

  • FixedThreadPool
  • SingleThreadExecutor
  • CacheThreadPool

FixedThreadPool

FixedThreadPool被称为可重用固定线程数的线程池。
下面是FixedThreadPool的源代码

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

FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建FixedThreadPool时指定的参数nThreads。

当线程池大于corePoolSize时,keepAliveTime为多余的空闲线程等待新任务的最长时间,超过这个时间后多余的线程将会被终止。这里把keepAliveTime设置为0L,意味着多余的空闲线程会被立即终止。

该线程池采用的是LinkedBlockingQueue无界队列作为线程池的工作队列。

并且由于使用无界队列,maximumPoolSize、keepAliveTime、还有拒绝策略都是无效的。

SingleThreadExecutor

SingleThreadExecutor是使用单个worked线程的Executor。
下面是SingleThreadExecutor的源码

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

SingleThreadExecutor的corePoolSize和maximumPool被设置成1。其他参数和FixedThreadPool相同。SingleThreadExecutor也使用无界队列作为线程池的工作队列。

CacheThreadPool

CacheThreadPool是一个会根据需要创建新线程的线程池。
下面是创建CacheThreadPool的源代码

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

CachedThreadPool的corePoolSize被设置为0,即corePool为空,maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPool是无界的。这里把keepAliveTime设置为60L,意味着CacheThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。

CacheThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CacheThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CacheThreadPool会不断创建新线程,极端情况下,CacheThreadPool会因为创建过多线程而耗尽CPU和内存资源。