Java面试进阶:Java并发类库提供的线程池有哪几种? 分别有什么特点?

Executors 目前提供了 5 种不同的线程池创建配置:

newCachedThreadPool(),它是一种用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用 SynchronousQueue 作为工作队列。

newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将会有新的工作线程被创建,以补足指定的数目 nThreads。

newSingleThreadExecutor(),它的特点在于工作线程数目被限制为 1,操作一个无界的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态,并且不允许使用者改动线程池实例,因此可以避免其改变线程数目。

newSingleThreadScheduledExecutor() 和 newScheduledThreadPool(int corePoolSize),创建的是个 ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在於单一工作线程还是多个工作线程。

newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。

线程池内部实现:

Executor 框架的基本组成:

ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的扩展,主要是增加了调度逻辑。而 ForkJoinPool 则是为 ForkJoinTask 定制的线程池,与通常意义的线程池有所不同。

1)Executor 是一个基础的接口,其初衷是将任务提交和任务执行细节解耦

2)ExecutorService 则更加完善,不仅提供 service 的管理功能,比如 shutdown 等方法,也提供了更加全面的提交任务机制,如返回Future而不是 void 的 submit 方法。

3)Java 标准类库提供了ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。

4)Executors 则从简化使用的角度,为我们提供了各种方便的静态工厂方法。

 

设计与实现:

1)工作队列:负责存储用户提交的各个任务,

newCachedThreadPool——》SynchronousQueue可以是容量为 0 ,大小浮动;

newFixedThreadPool——》LinkedBlockingQueue固定大小线程池。

2)内部的“工作线程管理池”指保持工作线程的集合运行过程中管理线程创建、销毁。如newCachedThreadPool临时创建线程

线程池的工作线程被抽象为静态内部类 Worker,private final HashSet<Worker> workers = new HashSet<>();基于AQS实现。

3)ThreadFactory 提供创建线程逻辑。

4)任务提交时被拒绝,如线程池已经处于 SHUTDOWN 状态,Java 标准库提供了类似ThreadPoolExecutor.AbortPolicy等默认实现,也可以按照需求自定义。

以上构造体现在构造参数上:

1)corePoolSize,核心线程数,可以理解为长期驻留的线程数目,newFixedThreadPool 会将其设置为 nThreads,而对于 newCachedThreadPool 则是为 0。

2)maximumPoolSize,线程不够时能够创建的最大线程数,newFixedThreadPool,当然就是 nThreads,因为其要求是固定大小,而 newCachedThreadPool 则是 Integer.MAX_VALUE。

3)keepAliveTime 和 TimeUnit,指定额外的线程能够闲置多久,有些线程池不需要。

4)workQueue,工作队列,必须是 BlockingQueue。(BlockQueue存入任务队列时是没有阻塞,使用的是offer,无阻塞添加方法。BlockQueue取出任务队列时是有阻塞,有超时使用poll取值,无超时使用take阻塞方法取值)

线程池生命周期流程图:(Idle:闲置的懒散的)

Execute源码:


public void execute(Runnable command) {
…
  int c = ctl.get();
// 检查工作线程数目,低于corePoolSize则添加Worker
  if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
          return;
      c = ctl.get();
  }
// isRunning就是检查线程池是否被shutdown
// 工作队列可能是有界的,offer是比较友好的入队方式
  if (isRunning(c) && workQueue.offer(command)) {
      int recheck = ctl.get();
// 再次进行防御性检查
      if (! isRunning(recheck) && remove(command))
          reject(command);
      else if (workerCountOf(recheck) == 0)
          addWorker(null, false);
  }
// 尝试添加一个worker,如果失败意味着已经饱和或者被shutdown了
  else if (!addWorker(command, false))
      reject(command);
}

如何选择线程池:

1)避免任务堆积: newFixedThreadPool 是创建指定数目的线程,但是其工作队列是无界的,如果工作线程数目太少,导致处理跟不上入队的速度,这就很有可能占用大量系统内存,出现 OOM。诊断时,你可以使用 jmap 之类的工具,查看是否有大量的任务对象入队。

2)避免过度扩展线程:通常在处理大量短时任务时,使用缓存的线程池

3)线程泄漏:这种情况往往是因为任务逻辑有问题,导致工作线程迟迟不能被释放。(放在线程池中的线程要捕获异常,如果直接抛出异常,每次都会创建线程,也就等于线程池没有发挥作用,如果大并发下一直创建线程可能会导致JVM挂掉)

4)避免死锁

5)尽量避免在使用线程池时操作 ThreadLocal

6)线程数设置:如果主要都是计算等消耗CPU的任务则设置成N或者N+1,如果是IO问题则参考:
线程数 = CPU核数 × 目标CPU利用率 ×(1 + 平均等待时间/平均工作时间),时间并不能精准预计,需要根据采样或者概要分析等方式进行计。

7)系统资源限制,如果线程调用的资源是瓶颈则需扩展资源,如端口个数限制等

 

扩展AQS:J.U.C是基于AQS实现的,AQS是一个同步器,设计模式是模板模式。核心数据结构:双向链表 + state(锁状态),底层操作:CAS。流程是

AQS 内部数据和方法:

一个 volatile 的整数成员表征状态,同时提供了 setState 和 getState 方法

一个先入先出(FIFO)的等待线程队列,以实现多线程间竞争和等待,这是 AQS 机制的核心之一。

各种基于 CAS 的基础操作方法,以及各种期望具体同步结构去实现的 acquire/release 方法。

ReentrantLock 为例,它内部通过扩展 AQS 实现了 Sync 类型,以 AQS 的 state 来反映锁的持有情况,线程间竞争则是 AQS 通过 Waiter 队列与 acquireQueued 提供的,cquireQueued 的逻辑,简要来说,就是如果当前节点的前面是头节点,则试图获取锁,一切顺利则成为新的头节点;否则,有必要则等待

1、synchronized在JVM中是会进行锁升级和降级的,并且是基于CAS来掌握竞争的情况,在竞争不多的情况下利用CAS的轻量级操作来减少开销。
2、而AQS也是基于CAS操作队列的,位于队列头的节点优先获得锁,其他的节点会被LockSupport.park()起来(这个好像依赖的是操作系统的互斥锁,应该也是个重量级操作)。
我觉得这两种方式都是基于CAS操作的,只是操作的对象不同(一个是Mark Word,一个是队列节点),当竞争较多时,还是不可避免地会使用到操作系统的互斥锁。然而,我再测试这两者的性能时,在无竞争的情况下,两者性能相当,但是,当竞争起来后,AQS的性能明显比synchronized要好

AQS对线程的挂起唤醒是通过locksupport实现的,而wait基于monitor;一般用并发库就不用Object.wait、notify之类了

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章