一、线程池
1、线程池的重要性
线程池的"池"是解决资源分配的,好处是可以复用线程,也可以控制资源总量
如果不使用线程池,那么:
一个线程处理
/**
* 每一个任务开一个线程
*/
public class EveryTaskOneThread {
public static void main(String[] args) {
Thread thread = new Thread(new Task(){{run();}});
thread.start();
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println("执行了任务");
}
}
}
for循环开启线程处理
/**
* For循环创建线程
*/
public class Forloop {
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(new Task(){{run();}});
thread.start();
}
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println("执行了任务");
}
}
}
我们希望有固定数量的线程,来执行这1000个线程,这样就避免了反复创建销毁线程所带来的开销问题。
2、为什么要使用线程池
问题一:反复创建线程开销大
问题二:过多的线程会占用太多内存
解决以上两个问题的思路
用量的线程—避免内存占用过多
让这部分线程都保特工作,且可以重复执行任务—避免生命周期的损耗
3、线程池的好处
加快响应速度
合理利用CPU和内存
统一管理资源
4、线程池适合应用的场合
服务器接受到大量请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率
实际上,在开发中,如果需要创建5个以上的线程,那么就可以使用线程池来管理
二、创建和停止线程池
1、线程池构造函数的参数
参数名 | 类型 | 含义 |
corePoolSize | int | 核心线程数 |
maxPoolSize | int | 最大线程数 |
keepAliveTime | Long | 保持存活时间 |
workQueue | BlockingQueue | 任务队列 |
threadFactory | ThreadFactory | 线程工厂 |
handler | RejectedExecutionHandler | 拒绝策略 |
CorePoolSize 核心线程数
corePoolSize指的是核心线程数:线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务的到来时,再创建新线程去执行任务,这个时候创建的线程数就等于要去执行任务的数量。当创建新线程完毕后,无论是否有任务,创建的线程会一直存活下来不会减少到核心线程数以下。(除非有异常情况发生)
MaxPoolSize 最大线程数
maxPoolSize指的是最大线程数:有些情况下任务越来越多,创建的线程数也应该有个限制,那么核心线程会处理最先到来的任务,新到来的任务会被放到任务队列中(workQueue),这时候任务还没有突破核心线程数+任务队列的总和,核心线程会把任务执行完后,再去任务队列中继续拿任务去执行。当任务越来越多以至于要执行的任务数>核心线程数+任务队列 的时候,于是线程池就会增加线程数到最大线程数去执行任务。
添加任务规则
增加线程的特点
通过设置corePoolSize和maxPoolSize相同,就能创建固定大小的线程池
线程池希望保持较少的线程池,并且只有再负载变的很大时才增加它
通过设置maxPoolSize为很高的值,比如是Integer.Max_VALUE,此线程池能容纳任意数量的并发任务
只有再队列填满的时候才会创建多于corePoolSize的线程,所以如果使用的是无界队列(LinkedBlockingQueue),那么线程数就不会超过corePoolSize
KeepAliveTime 存活时间
如果线程池当前线程数多于corePoolSize,那么如果多于的线程空闲时间超过keepAliveTime,它们就会被终止。这是一种机制,这种机制是解决线程数过多冗余的时候,造成资源消耗,主要回收的是超过核心线程的线程,但是如过设置参数:***allowCoreThreadTimeOut(true)***核心线程也会被回收
ThreadFactory 线程工厂
新的线程是由ThreadFactory创建的,默认使用Executors.defaultThreadFactory(),创建出来的线程都在同一个线程组,拥有同样的NORM_PRIORITY(5)优先级并且都不是守护线程。如果自己指定ThreadFactory,那么就可以改变线程名、线程组、优先级、是否是守护线程等。
通常我们用默认的ThreadFactory就可以了
WorkQueue 工作队列
有三种最常见的队列类型:
直接交接:SynchronousQueue
这个队列存在的意义是只是对任务做下中转,所以队列是不能存储任务的。在使用这个队列的时候maxPoolSIze要设置的大一点,因为没有队列进行缓冲,会很快的创建线程执行任务。
无界队列:LinkedBlockingQueue
这个队列的队列是不会被填满的,所以maxPoolSize设置多少都是没用的,这种情况下能解决任务数量激增,任务处理不完都存
储再队列中不过这种队列使用可能有风险,如果现场处理任务的速度跟不上队列存储任务的速度的时候,可能会造成OOM。
有界的队列:ArrayBlockingQueue
这个队列可以设置队列大小的,这种情况下现场池中maxPoolSize就有意义了,也是最经常用到的队列
2、线程池应该手动创建还是自动创建
常用自动创建线程池
FixesThreadPool:固定线程池
public class FixedThreadPoolTest {
public static void main(String[] args) {
//创建固定线程池,设置线程数为4
ExecutorService executorService = Executors.newFixedThreadPool(4);
//循环去执行任务
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
static class Task implements Runnable{
//休息一点时间后,执行任务,任务是打印出当前执行现场的名字
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
最后发现只有四个线程再执行任务
创建原理
//第一个参数corePoolSize和第二个参数maxPoolSize为传进来线程数 证明线程永远是定长
//第三个参数超时为0L就是没有超时时间,线程是不会被回收的
//第五个参数是无界队列,无论任务有多少都会被塞进这个任务队列中去,所以是一直是核心线程再执行任务
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点
由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。
例:
/**
* 固定线程池OOM练习
*
* @author liuzhe
* @date 2023/02/09
*/
public class FixedThreadPoolOOMTest {
/**
* 设置固定线程池
*/
private static ExecutorService executorService = Executors.newFixedThreadPool(1);
public static void main(String[] args) {
//任务循环一直创建,都会存储再任务队列中
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executorService.execute(new SubThread());
}
}
static class SubThread implements Runnable{
//完成任务的时间调整到最大,这样任务就会一直堆集
@Override
public void run() {
try {
Thread.sleep(1000000000000000000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
SingleThreadExecutor:单线程线程池
public class SingleThreadExecutor {
public static void main(String[] args) {
//创建一个单线程线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
static class Task implements Runnable{
//休息一点时间后,执行任务,任务是打印出当前执行线程的名字
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
创建原理
//第一个参数corePoolSize和第二个参数maxPoolSize为1 证明线程永远是定长
//第三个参数超时为0L就是没有超时时间,线程是不会被回收的
//第五个参数是无界队列,无论任务有多少都会被塞进这个任务队列中去,所以是一直是核心线程再执行任务
//单线程线程池是属于定长线程线程池,只不过单线程线程池是永远只有一个线程执行任务
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特点
由于传进去的LinkedBlockingQueue是没有容量上限的,所以当请求数越来越多,并且无法及时处理完毕的时候,也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。
它只会用唯一的工作线程执行任务
原理和FixedThreadPool一样,但是此时线程数量被设置为了1
CachedThreadPool:可缓存线程池
/**
* 缓存线程池
*
* @author liuzhe
* @date 2023/02/15
*/
public class CachedThreadPool {
public static void main(String[] args) {
//创建可缓存线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.execute(new Task());
}
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
创建原理
//第一个参数corePoolSize,核心线程数量是0;第二个参数maxPoolSize是Integer.MAX_VALUE,基本上最大线程数是用不完的,每次执行任务最大线程数都会+1
//第三个参数过期时间为60,第四个参数过期时间单位秒,就是如果有线程超过60s空闲时间,该线程就会被回收
//第五个参数任务队列用的是SynchronousQueue,这个队列是无界的不会存储数据的,只要有一个任务要执行,这个线程池既不会创建核心线程,也不会把该任务存储到任务队列中,而是创建最大线程,由最大线程来执行任务
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点
弊端是第二个参数maxPoolSize是Integer.MAX_VALUE,这可能会创建非常多的线程,导致OOM
可以回收线程
ScheduledThreadPool:定时任务线程池
/**
* 定时线程池
*
* @author liuzhe
* @date 2023/02/15
*/
public class ScheduledThreadPool {
public static void main(String[] args) {
//创建定时线程池
ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
//设置延迟定时任务,delay参数的意思是延迟时间多少时间单位后执行该线程
threadPool.schedule(new Task(),5,TimeUnit.SECONDS);
//设置间隔定时任务,initialDelay参数的意思是开始多久后执行该线程;period的意思是第一次执行任务后,每隔多久后继续执行任务
threadPool.scheduleAtFixedRate(new Task(),1,3,TimeUnit.SECONDS);
}
static class Task implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
创建原理
//第一个参数corePoolSize为手动输入的
//第二个参数maxPoolSize为Integer.MAX_VALUE,最大线程数几乎是无限的,当任务时间紧急的时候可能因为创建现场过多造成OOM
//第三个参数和第四个参数说明定时线程池不具备自动回收线程功能
//弟五个参数任务队列说明此线程池用的是延迟任务队列
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
特点
定时执行任务
以上4中线程池的构造函数的参数
正确的创建现场池的方法
根据不同的业务场景,自己设置现场池参数,比如内存。
3、线程池里的线程数量设定多少比较合适?
4、停止线程的方法
1.shutdown/isShutdown/isTerminated
代码
/**
* 演示关闭线程池1
*
* @author liuzhe
* @date 2023/02/16
*/
public class ShunDwonTask1 {
public static void main(String[] args) {
//创建一个定长为10的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
//创建100个任务,用这个线程池去执行
for (int i = 0; i < 100; i++) {
executorService.execute(new ShutDownTask());
}
//休息1.5s后执行shotdown这个方法
Thread.sleep(1500);
//执行showdown方法前,调用isShutdown来判断这个线程是否进入停止状态
System.out.println("线程池是否进入停止状态: " + executorService.isShutdown());
//执行showdown方法前,isTerminated来判断这个线程是否进入停止状态
System.out.println("线程池是否已经开始停止并且所有任务已经执行完毕: " + executorService.isTerminated());
executorService.shutdown();
//执行showdown方法后,再新增一个任务让线程池去执行,这时线程池就会拒绝添加这个任务不会执行
executorService.execute(new ShutDownTask());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//执行showdown方法后,判断线程是否进入停止状态
System.out.println("线程池是否进入停止状态: " + executorService.isShutdown());
//执行showdown方法后,isTerminated来判断这个是否已经停止并且所有任务已经执行完毕
System.out.println("线程池是否已经开始停止并且所有任务已经执行完毕: " + executorService.isTerminated());
//休息10后,isTerminated来判断这个线程是否已经停止
try {
Thread.sleep(10000);
System.out.println("线程池是否已经开始停止并且所有任务已经执行完毕: " + executorService.isTerminated());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class ShutDownTask implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
特点
shutdown方法是将线程池设置为停止状态,线程池不会马上关闭,正在执行的任务和在任务队列中存储的任务还是会执行。但是有新任务进来会报异常,
isShutdown方法是判断现场是否处于停止状态,调用shutdown()方法后返回结果为true,此方法只是判断线程池是否处于这个状态,不能判断任务是否执行完毕
isTerminated方法来判断这个是否已经停止并且所有任务已经执行完毕
2.awaitTermination
代码
public class ShunDwonTask2 {
public static void main(String[] args) {
//创建一个定长为10的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
//创建100个任务,用这个线程池去执行
for (int i = 0; i < 100; i++) {
executorService.execute(new ShutDownTask());
}
//休息1.5s后执行shotdown这个方法
Thread.sleep(1500);
executorService.shutdown();
//注意:awaitTermination方法再返回前是阻塞的,有3种情况有返回值
//1:所有任务都执行完毕
//2:所有任务执行前被中断
//2:到时时间,任务是否已经全部执行完毕
//3s后,线程池是否已经完全执行完任务
boolean a = executorService.awaitTermination(3, TimeUnit.SECONDS);
System.out.println("3s后,线程池是否已经完全执行完任务: " + a);
//15s后,线程池是否已经完全执行完任务
boolean b = executorService.awaitTermination(15, TimeUnit.SECONDS);
System.out.println("15s后,线程池是否已经完全执行完任务: " + b);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class ShutDownTask implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
特点
awaitTermination方法判断在多次时间后,此线程是否已经停止,并且任务队列中任务全部完成
awaitTermination方法再返回前是阻塞的,有3种情况有返回值
所有任务都执行完毕
:所有任务执行前被中断
到时时间,任务是否已经全部执行完毕
这三种情况返回true,否则返回fasle
3.shutdownNow
代码
public class ShunDwonTask3 {
public static void main(String[] args) {
//创建一个定长为10的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
//创建100个任务,用这个线程池去执行
for (int i = 0; i < 100; i++) {
executorService.execute(new ShutDownTask());
}
//休息1.5s后执行shutdownNow这个方法
Thread.sleep(1500);
//正在执行任务的线程会立即中断,并且每个线程都抛出异常 sleep interrupted
//队列中没有执行的线程会变成返回值返回
List<Runnable> runnables = executorService.shutdownNow();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class ShutDownTask implements Runnable{
@Override
public void run() {
try {
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断了");
}
}
}
}
特点
shutdownNow方法会将正在执行任务的线程会立即中断,并且每个线程都抛出异常 sleep interrupted、队列中没有执行的线程会变成返回值返回
5、任务太多,怎么拒绝
拒绝时机
当Executor关闭时,提交新任务会被拒绝(例如:调用shutdown或者shutdownNow方法后,又开始调用此线程池的execute方法)
以及当Executor对最大线程和工作队列容量使用有限边界并已经饱和时
拒绝策略
AbortPolicy
直接抛出异常,并且拒绝执行
DiscardPolicy
不抛出异常,但是会把任务丢弃,不知道新提交任务是否执行
DiscardOldestPolicy
抛弃出等待时间最长没有执行的任务,不知道新提交任务是否执行
CallerRunsPolicy
谁提交的这个任务,谁来执行这个线程(主线程)去执行这个任务。任务执行可以避免业务损失,可以给线程池一定缓冲时间