参考:https://blog.csdn.net/wtopps/article/details/80682267
前言
在互联网的开发场景下,很多时候需要我们用到多线程解决问题。从 Java 5 开始,Java 提供了自己的线程池,线程池就是一个线程的容器,每次只执行额定数量的线程。java.util.concurrent包中提供了ThreadPoolExecutor类来管理线程,本文将介绍一下ThreadPoolExecutor类的使用。
为什么要使用线程池
在执行一个异步或者并发任务时,往往使用new Thread()方式创建新的线程,这样多有很多弊端,更好的方法是合理利用线程池,线程池有很多优势:
- 降低系统消耗,通过重用已存在的线程,降低线程创建和销毁带来的资源消耗;
- 提高系统响应速度,当有新任务到达时,无需等待线程的创建即可直接执行;
- 方便线程并发数的管控,线程如果无限制的创建,不仅会造成大量额外的系统消耗,更有可能占用太多资源阻塞系统,从而降低系统的稳定性。线程池能有效的管控线程,统一分配,调优,提高资源利用率;
- 线程池提供了更强大的功能:包括定时,定期,可控线程数量等,并且操作十分简单。
线程池的使用方式
java.util.concurrent包中提供了多种线程池的创建方式,我们可以直接使用ThreadPoolExecutor类直接创建一个线程池,也可以使用Executors类创建,下面我们分别说一下这几种创建的方式。
一、Executors创建线程池
Executors类是java.util.concurrent提供的一个创建线程池的工厂类,使用该类可以方便的创建线程池,此类提供的几种方法,支持创建四种类型的线程池,分别是:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor。
newCachedThreadPool
创建一个可缓存的无界线程池,该方法没有参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE,可看做是无限大:
/**
* 创建无边界大小的线程池
*/
public static void createCachedThreadPool(){
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i <10 ; i++) {
final int currentIndex = i;
cachedThreadPool.execute(()-> {
System.out.println(Thread.currentThread().getName()+",currentIndex is:"+currentIndex);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("全部线程执行完毕!");
}
上面的Demo中创建了一个无边界限制的线程池,同时使用了一个多线程辅助类CountDownLatch,关于该类的使用,后面会有介绍。执行结果如下:
pool-1-thread-1,currentIndex is:0
pool-1-thread-2,currentIndex is:1
pool-1-thread-3,currentIndex is:2
pool-1-thread-4,currentIndex is:3
pool-1-thread-5,currentIndex is:4
pool-1-thread-6,currentIndex is:5
pool-1-thread-7,currentIndex is:6
pool-1-thread-8,currentIndex is:7
pool-1-thread-9,currentIndex is:8
pool-1-thread-10,currentIndex is:9
全部线程执行完毕!
newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享无界队列的方式来运行这些线程,如果在所有线程都处于活动状态时提交附加任务,则在有可用线程之前,附加任务在队列中等待。
/**
* 创建固定大小的线程池
*/
public static void createFixedThreadPool() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
final CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int currentIndex = i;
fixedThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
});
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
pool-1-thread-2, currentIndex is : 1
pool-1-thread-3, currentIndex is : 2
pool-1-thread-1, currentIndex is : 0
pool-1-thread-4, currentIndex is : 3
pool-1-thread-5, currentIndex is : 4
全部线程执行完毕
newScheduledThreadPool
创建一个线程池,它可安排在给定延时之后执行或定期执行任务。
/**
* 创建给定延迟后运行命令或者定期地执行的线程池
*/
public static void createScheduledThreadPool(){
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
final CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
final int currentIndex = i;
//定时执行一次的任务,延迟1s之后执行
scheduledThreadPool.schedule(() -> {
System.out.println(Thread.currentThread().getName() + ", currentIndex is : " + currentIndex);
countDownLatch.countDown();
}, 1, TimeUnit.SECONDS);
//周期性执行任务,延时2s,每3s执行一次任务
scheduledThreadPool.scheduleAtFixedRate(()->{
System.out.println(Thread.currentThread().getName()+"eve 3s");
},2,3,TimeUnit.SECONDS);
}
}
pool-1-thread-1, currentIndex is : 0
pool-1-thread-2, currentIndex is : 1
pool-1-thread-3, currentIndex is : 2
pool-1-thread-2, currentIndex is : 3
pool-1-thread-4, currentIndex is : 4
pool-1-thread-5every 3s
pool-1-thread-2every 3s
pool-1-thread-3every 3s
pool-1-thread-1every 3s
pool-1-thread-5every 3s
pool-1-thread-2every 3s
pool-1-thread-4every 3s
pool-1-thread-4every 3s
pool-1-thread-3every 3s
pool-1-thread-2every 3s
该线程池提供了很多方法:
- schedule(Runnable command, long delay, TimeUnit unit):延时一定时间后,执行Runnable任务;
- schedule(Callable callable, long delay, TimeUnit unit):延时一定时间后,执行Callable任务;
- scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):延时一定时间后,以间隔period时间的频率周期性的执行任务;
- scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit):与scheduleAtFixedRate()方法很类似,但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,也就是这一些任务系列的触发时间都是可预知的。
newSingleThreadExecutor
创建一个单线程的线程池,以无界队列(长度不受限制的队列)的方式来运行该线程。当多个任务提交到单线程线程池,线程池将逐个去执行,未执行的任务将放入无界队列中等待。
/**
* 创建单线程的线程池
*/
public static void createSingleThreadPool(){
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
pool-1-thread-1
四种线程池的对比
线程池方法 | 初始化线程数 | 最大线程数 | 线程池中线程存活时间 | 时间单位 | 工作队列 |
---|---|---|---|---|---|
newCachedThreadPool | 0 | Integet.MAX_VALUE | 60 | 秒 | SynchronousQueue |
newFixedThreadPool | 参数决定 | 线程数固定 | 0 | 毫秒 | LinkedBlockingQueue |
newScheduledThreadPool | 参数决定 | Integet.MAX_VALUE | 0 | 微妙 | DelayedWorkQueue |
newSingleThreadExecutor | 1 | 1 | 0 | 毫秒 | LinkedBlockingQueue |
二、ThreadPoolExecutor创建线程池
Executors类提供四个静态工厂方法:newCachedThreadPool(),newFixedThreadPool(int),newScheduledThreadPool(int),newSingleThreadPool()。这些方法最终都是通过ThreadPoolExecutor类来完成的,当有一些场景需要更细粒度的控制线程池,可以使用ThreadPoolExecutor()方法创建线程池。
/**
* 使用ThreadPoolExecutor创建线程池
*/
public static void createThreadPoolExecutor(){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
5,
10,
10L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(1000),
new ThreadPoolExecutor.AbortPolicy());
final CountDownLatch countDownLatch = new CountDownLatch(8);
for (int i = 0; i < 8; i++) {
final int currentIndex = i;
System.out.println("提交第"+i+"个线程");
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+",currentIndex:"+currentIndex);
countDownLatch.countDown();
});
}
System.out.println("全部提交完毕");
try {
System.out.println("准备等待线程池任务执行完毕");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全部线程执行完毕");
}
提交第0个线程
提交第1个线程
提交第2个线程
提交第3个线程
提交第4个线程
pool-1-thread-1,currentIndex:0
提交第5个线程
pool-1-thread-2,currentIndex:1
pool-1-thread-2,currentIndex:5
提交第6个线程
提交第7个线程
pool-1-thread-2,currentIndex:6
pool-1-thread-1,currentIndex:7
全部提交完毕
准备等待线程池任务执行完毕
pool-1-thread-3,currentIndex:2
pool-1-thread-4,currentIndex:3
pool-1-thread-5,currentIndex:4
全部线程执行完毕
接下来看一下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;
}
- corePoolSize - 池中所保存的线程数,包括空闲线程,必须大于或等于0。
- maximumPoolSize - 池中允许的最大线程数,必须大于或等于corePoolSize。
- keepAliveTime - 线程存活时间,当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
- unit - keepAliveTime 参数的时间单位,必须大于或等于0。
- workQueue - 工作队列,执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
- threadFactory - 执行程序创建新线程时使用的工厂,默认为DefaultThreadFactory类。
- handler - 拒绝策略,由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序,默认策略为ThreadPoolExecutor.AbortPolicy。
各个参数详细解释
1.corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,才会根据是否存在空闲线程,来决定是否需要创建新的线程。也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。
2.maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
3.keepAliveTime(线程存活保持时间):默认情况下,当线程池的线程个数多于corePoolSize时,线程的空闲时间超过keepAliveTime则会终止。但只要keepAliveTime大于0,allowCoreThreadTimeOut(boolean) 方法也可将此超时策略应用于核心线程。另外,也可以使用setKeepAliveTime()动态地更改参数。
4.unit(存活时间的单位):时间单位,分为7类,从细到粗顺序:NANOSECONDS(纳秒),MICROSECONDS(微妙),MILLISECONDS(毫秒),SECONDS(秒),MINUTES(分),HOURS(小时),DAYS(天);
5.workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。可以使用此队列与线程池进行交互:
- 如果运行的线程数少于corePoolSize,则Executor首选添加新线程,而不选择排队;
- 如果运行的线程数大于或等于corePoolSize,则Exector始终首选将请求加入队列,而不添加新线程;
- 如果无法将请求加入队列,则创建新线程。除非线程数超过maxmumPoolSize,在这种情况下,任务将被拒绝。
6.threadFactory(线程工厂):用于创建新线程。由同一个threadFactory创建的线程,属于同一个ThreadGroup,创建的线程优先级都为Thread.NORM_PRIORITY。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格,pool-m-thread-n(m为线程池编号,n为线程池内的线程编号);
7.handler(线程饱和策略):当线程池和队列都满了,则表明该线程池已达饱和状态。
- ThreadPoolExecutor.AbortPolicy:处理程序遭到拒绝,则直接抛出运行时异常RejectedExecutionException。(默认策略)
- ThreadPoolExecutor.CallerRunsPolicy:调用者所在线程来运行该任务,此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
- ThreadPoolExecutor.DiscardPolicy:无法执行的任务将被删除。
- ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重新尝试执行任务(如果再次失败,则重复此过程)。