JAVA并发编程系列(11)线程池底层原理架构剖析
面试官:说说JAVA线程池的几个核心参数?
之前我们用了10篇文章详细剖析了synchronized、volatile、CAS、AQS、ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier、并发锁、Condition等各个核心基础原理,今天开始我们说说并发领域的各种工具包还有应用场景。
1、为什么要用线程池?
日常频繁创建和销毁线程是非常消耗系统资源的操作,会降低系统的整体性能。而线程池通过池化的管理思想,把线程的创建、任务调度、任务执行、任务等待、任务拒绝、销毁等全生命周期各个环节都进行统一管理,大幅提升系统性能,提高线程利用率以及任务响应。
2、线程池核心参数意义
一共有6个:
private volatile ThreadFactory threadFactory;
private volatile RejectedExecutionHandler handler;
private volatile long keepAliveTime;//非核心线程空闲超时时间
private volatile boolean allowCoreThreadTimeOut;
private volatile int corePoolSize;//核心线程数量
private volatile int maximumPoolSize;//最大线程数量
2.1 corePoolSize 核心线程数量
核心线程即使空闲,也不会被回收。如果当前线程数量小于corePoolSize,即使其他核心线程空闲,线程池也会新增创建一个线程来执行任务。
核心线程会一直存活在线程池里,但是如何设置了线程池的allowCoreThreadTimeOut为true,则核心线程空闲一定时间也会被回收。
private volatile boolean allowCoreThreadTimeOut
2.2 maximumPoolSize最大线程数量
线程池最大线程数量maximumPoolSize = 核心线程数量corePoolSize + 非核心线程数量。
场景A:当线程数量>=核心线程数量,且等待队列未满,将任务加入到等待队列。
场景B:当队列已满,并且线程数量<maximumPoolSize ,就新增非核心线程。这种非核心线程就是在空闲时间大于keepAliveTime,就会被回收。
场景C:当队列已满,且线程数量大于等于maximumPoolSize,就触发拒绝策略handler拒绝任务。
2.3 keepAliveTime 线程空闲时间
keepAliveTime允许线程的最大的空闲存活时间。如果一个非核心线程在空闲状态下持续超过keepAliveTime了,就会被回收,以及线程池设置allowCoreThreadTimeOut为true,核心线程也会被回收。
2.4 workQueue任务存储队列
用于存储等待执行的任务。主要有三种类型。
第一个,直接提交SynchronousQueue,把任务直接提交给工作线程而不放到等待队列。这个队列不存储元素,newCachedThreadPool使用的就是这种同步移交队列,吞吐量比LinkedBlockingQueue大。
第二个,LinkedBlockingQueue无界队列,队列的最多容量为int的最大值,相当于无限容量。newFixedThreadPool和newSingleThreadPool使用的就是LinkedBlockingQueue,这个吞吐量比ArrayBlockingQueue高。
第三个,ArrayBlockingQueue有界队列,可以有效避免资源耗尽。
除了这三种,还有DelayQueue、PriorityBlockingQueue。
2.5 threadFactory线程工厂
线程工厂,用来创建线程。
2.6 handler拒绝策略
拒绝任务的策略。当线程数量达到最大线程数量maximumPoolSize,以及等待队列workQueue也满了,这时候需要用来拒绝任务提交时的策略handler。策略类型有抛异常、用调用者所在的线程执行任务、丢弃队列中第一个任务执行当前任务、直接丢弃任务)。
策略种类有:
AbortPolicy:直接抛出异常。
DiscardPolicy:丢弃任务,不抛异常。
DiscardOldestPolicy:将等待队列里等待最久的任务丢弃。
CallerRunsPolicy: 哪个线程提交的任务,哪个线程去处理。
4、有几种线程池?
JAVA线程池ThreadPoolExcutor,常见的有这四种线程池。
//初始化一个无限线程的线程池,无需等待队列
Executors.newCachedThreadPool()
//初始化一个支持周期性运行的线程池
Executors.newScheduledThreadPool(10);
//初始化固定数目线程的线程池
Executors.newFixedThreadPool(10);
//初始化仅有一个线程的线程池
Executors.newSingleThreadExecutor();
4.1 Executors.newCachedThreadPool()可缓存线程池
创建一个可缓存的线程池,如果线程数量大于处理任务时,空闲线程被回收;当提交任务增加时,又可以新建线程去处理任务。这种线程池的线程数无限制,corePoolSize数值为0, maximumPoolSize 的数值都是为 Integer.MAX_VALUE。
线程可复用性很高,可以减少频繁创建/销毁线程,减少系统开销。工作队列workQueue选用SynchronousQueue。
4.2 Executors.newScheduledThreadPool()定时调度线程池
也是固定长度的线程池,但是支持以延迟或者定时的方式去执行任务。
4.3 newSingleThreadExecutor()单线程的线程池
一个线程,corePoolSize 和 maximumPoolSize 的数值都是为 1,线程池里只有一个工作线程执行任务。若这个唯一的线程异常出问题了,会新建另一个线程来替代。所有任务在等待队列是FIFO顺序被执行。
4.4 Executors.newFixedThreadPool()固定长度线程池
每次提交任务的时候就会创建一个新的线程,直到达到线程池的最大数量限制,如何任务大于线程数量,就进入等待队列。 corePoolSize 和 maximumPoolSize 的数值相等。工作队列选用LinkedBlockingQueue。
最后,我们上一个线程池demo,固定线程池,每次最多N个线程在执行任务,其他任务等待。
package lading.java.mutithread;
import cn.hutool.core.date.DateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 固定线程池,线程数量为3
* 提交多个count+1任务计算
*
*/
public class Demo013ThreadPool {
//计算任务count
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
//固定数量线程池
ExecutorService service = Executors.newFixedThreadPool(3);
Executors.newScheduledThreadPool(10);
//提交20次,对count+1的任务
for (int i = 1; i < 20 + 1; i++) {
service.submit(new Thread(() -> {
//模拟每次计算耗时2s
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//完成本次对count+1计算任务
System.out.println(DateTime.now().toString("YYYY-MM-dd hh:mm:ss") + " " +Thread.currentThread().getName() + "线程 执行计算任务:" + count.incrementAndGet());
}));
}
service.shutdown();
}
}
每次只有三个线程在运行任务,其他任务等之前任务执行完成后再执行。
今天就分享到这,明天分享并发容器CurrentHashMap。