一、线程池的概念
顾名思义就是事先准备一个池子(线程池),初始化一些线程,当使用可以快速调用,不用再初始化线程,使用完成后,不再销毁该线程,归还到线程池,方便后面复用。节省创建和销毁线程资源,提高线程使用效率。
二、线程池的好处
1)节省资源
2)提高线程执行效率
3)提高了线程管理效率,更方便管理调度线程
三、线程池原理
3.1 ThreadPoolExecutor 七大核心参数
1)int corePoolSize
核心线程池大小 (核心线程最大数量)
核心线程:线程池中有两类线程:核心线程和非核心线程。核心线程默认情况下会一直存在于线程中,即使这个核心线程什么都不干,而非核心线程如果长时间的闲置,就会被销毁。
2)int maximumPoolSize
最大线程池大小 (线程总数量最大值),该值等于核心线程数+非核心线程数
3)long keepAliveTime
线程池中超过corePoolSize数目的空闲线程最大存活时间(非核心线程的闲置超时时间)
4)TimeUnit unit
时间单位(keepAliveTime的时间单位)
TimeUnit是一个枚举类型:MILLSECONDS:1毫秒、SECONDS:1秒、MINUTES:1分。。。
5)BlockingQueue<Runnable> workQueue
线程等待队列(阻塞队列)
常见的几个阻塞队列:
1、LinkedBlockingQueue
链式阻塞队列,默认大小:Interger.MAX_VALUE,可以指定大小。
2、ArrayBlockingQueue
数组阻塞队列,需要指定大小
3、SynchronousQueue
同步队列,内部容量为0,每个put操作都必须等待一个take操作。反之亦然
4、DelayQueue
延迟队列,队列中的元素之后当其指定的延迟时间到了,才能从队列中获取到改元素。
5、PriorityBlockingQueue
优先阻塞队列:无界,默认采用元素自然顺序升序排列。
6)ThreadFactory threadFactory
线程工厂:创建线程的工厂,用于批量创建线程,如果不指定,会新建一个默认的线程工厂。
7)RejectedExecutionHander handler
拒绝处理策略,当无法创建新线程处理任务并且阻塞队列已满时就会采用拒绝处理策略。
jdk默认四种策略:
ThreadPoolExecutor.AbortPolicy:默认拒绝处理策略,丢弃任务并抛出异常
ThreadPollExecutor.DiscardPolicy:丢弃新来的任务,但不抛出异常
ThreadPollExecutor.DiscardOldestPolicy:丢弃阻塞队列头部(最旧)的任务,然后重新尝试执行程序,(如果再次失败,重复此过程)
ThreadPollExecutor.CallerRunPolicy:由调用的线程去处理改任务。只适用于并发小的情况。
3.2 corePoolSize,maximumPoolSize,workQueue之间关系。
1、当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2、当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3、当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4、当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理 (拒绝策略)
5、当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6、当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
四、创建线程池的方式
4.1 固定长度线程池:(指定工作线程数量的线程池)——newFixedThreadPool
重用指定数目(nThreads)的线程,其背后使用 的是无界的工作队列,任何时候最多有 nThreads 个工作线程是活动的。这意味着,如果任务数量超过了活动队列数目,将在工作队列中等待空闲线程出现;如果有工作线程退出,将 会有新的工作线程被创建,以补足指定的数目 nThreads;
适用场景:可用于Web服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。
package com.aaa.day03_threadPool.demo1;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author :caicai
* @date :Created in 2022/7/20 13:31
* @description:固定长度线程池
* @description: 适用场景:可用于Web服务瞬时削峰,但需注意长时间持续高峰情况造成的队列阻塞。
*/
public class FixedThreadPoolDemo {
public static void main(String[] args) {
// 实例化一个固定长度为3的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 20; i++) {
// 使用ExecutorService.execute启动线程
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"正在执行。。。");
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
结果展示:
4.2 单独线程池:(单线程化的线程池)——newSingleThreadPool
它的特点在于工作线程数目被限制为 1,操作一个无界 的工作队列,所以它保证了所有任务的都是被顺序执行,最多会有一个任务处于活动状态, 并且不允许使用者改动线程池实例,因此可以避免其改变线程数目
适用场景: 想让任务高速顺序执行时,可以使用
package com.aaa.day03_threadPool.demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author :caicai
* @date :Created in 2022/7/20 13:55
* @description:单独线程池
* @description:适用场景: 想让任务高速顺序执行时,可以使用
* @description:他的特点在于工作线程数目被限制为1,操作一个无界的工作队列,所以他保证了所有任务的都是被顺序执行
* 最多会有一个任务处于活动状态,并且步允许使用者改动线程池实例,因此可以避免其改变线程数目
* @modified By:
* @version:
*/
public class SingleThreadPool {
public static void main(String[] args) {
// 实例化对象
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 20; i++) {
// 启动线程
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"正在执行。。。。");
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
结果展示
4.3 缓存线程池(可缓存线程池)——newCachedThreadPool
它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果 线程闲置的时间超过 60 秒,则被终止并移出缓存;长时间闲置时,这种线程池,不会消耗 什么资源。
应用场景:它是一种用来处理大量短时间工作任务的线程池
package com.aaa.day03_threadPool.demo1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author :caicai
* @date :Created in 2022/7/20 13:45
* @description:缓存线程池
* @description:应用场景:它是一种用来处理大量短时间工作任务的线程池
* @description:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;
* 如果线程闲置的时间超过60秒,则被终止并移除缓存;长时间闲置时,这种线程池,不会消耗什么资源
* @modified By:
* @version:
*/
public class CacheThreadPool {
public static void main(String[] args) {
// byte 1Byte =8bit 0000 0000 -> 1111 1111 2^8 =256 -128 - 127
// int 4byte =32bit 2^32 -2^31 - 2^31
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
// 使用ExecutorService.execute启动线程
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"正在执行了。。。。");
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
结果展示
4.4 可调度线程池(定时线程池)——newScheduledThreadPool
可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程
应用场景:定时或周期性任务执行
package com.aaa.day03_threadPool.demo1;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author :caicai
* @date :Created in 2022/7/20 14:08
* @description:可调度线程池
* @modified By:
* @version:
*/
public class ScheduledThreadPool {
public static void main(String[] args) {
// 实例化线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
// 让当前运行的线程延迟5秒后开始执行
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"正在执行。。。。");
}
},3, TimeUnit.SECONDS);
// 让当前运行的线程延迟5秒后开始执行,并且以后每隔3秒执行一次 FixedRate 两次开始执行之间的间隔
// scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
// @Override
// public void run() {
// System.out.println("线程"+Thread.currentThread().getName()+"开始执行。。。。"+new Date());
// }
// },2,3,TimeUnit.SECONDS);
// 让当前运行的线程延迟2秒后开始执行,并且以后每隔3秒执行一次
// FixedDelay 上次任务结束到下次任务开始间隔时间
/*scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("线程"+Thread.currentThread().getName()+"开始执行。。。"+new Date());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程"+Thread.currentThread().getName()+"执行结果。。。"+new Date());
}
},2,3,TimeUnit.SECONDS);*/
}
}
测试结果:
五、submit和execute区别
本质区别是Runnable和 Callable区别
Submit和Execute区别:
1)execute 参数只能是Runnable的实现类 submit 参数既能是Runnable的实现类 可以是Callable的实现类
2)execute 没有返回值 submit有返回值,并且可以通过 future 的get() 获取
3)execute 无法处理异常 submit 可以处理异常,并且可以通过 future 的get() 打印异常堆栈信息
代码展示:
package com.aaa.day03_threadPool.demo2;
import java.util.UUID;
import java.util.concurrent.*;
/**
* @author :caicai
* @date :Created in 2022/7/20 14:34
* @description:submit和execute区别
* 1、execute 参数只能是Runnable的实现类 submit 参数既能是Runnable的实现类 也可以是Callable的实现类
* 2、execute 没有返回值 submit有返回值,并且可以通过 future 的get() 获取
* 3、execute 无法处理异常 submit 可以处理异常,并且
*/
public class SubmitAndExecute {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 实例化一个固定长度为3的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 区别1
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("参数只能是Runnable的实现类。。。");
System.out.println("===============================================");
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("submit的参数既能是Runnable的实现类。。。");
}
});
executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
System.out.println("submit的参数还可以是Callable的实现类。。。。");
return null;
}
});
Future<?> future = executorService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("submit的参数还可以是Callable的实现类。。。");
String randomStr = UUID.randomUUID().toString();
System.out.println(randomStr);
//System.out.println(1/0);
return randomStr;
}
});
System.out.println("获取执行结果:"+future.get());
executorService.shutdown();
}
}
结果展示: