文章来源:https://blog.csdn.net/qq_52463861/article/details/127707656
一、线程存在的问题
1.创建线程需要开辟本地线程栈、虚拟机栈、程序计数器等私有线程内存,消耗的时候也需要释放这些内存,频繁的创建和销毁需要⼀定的开销;
2.当任务数远远⼤于线程可以承载的数量之后,不能友好的进⾏任务拒绝。
二、线程池是什么
线程池(ThreadPool)是⼀种使用池化技术管理和使⽤线程的机制。
它是将多个线程预先存储在⼀个“池子”内,当有任务出现时可以避免重新创建和销毁线程所带来性能开销,只需要从“池子”内取出相应的线程执行对应的任务即可。
池化思想的应用:
a. 连接池(Connection Pooling):预先申请数据库连接,提升申请连接的速度,降低系统的开销。
b. 实例池(Object Pooling):循环使⽤对象,减少资源在初始化和释放时的昂贵损耗。
三、线程池的优点
线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不⾜的问题。
如果不使⽤线程池,有可能造成系统创建⼤量同类线程⽽导致消耗完内存或者“过度切换”的问题。
具体如下:
a.使用线程,从而避免线程重复创建和销毁的性能开销。
b. 控制线程数量,从而避免了因为线程创建过多而导致的内存溢出问题。
c.提供了任务管理功能,从而可以实现任务缓存和任务拒绝的情况。
d.提供更多更强⼤的功能:线程池具备可拓展性,允许开发⼈员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执⾏或定期执⾏。
四、线程池的使用
线程池的创建方法共有7种,但可以分为两类:
a. 通过 ThreadPoolExecutor 手动 创建的线程池(1种);
b. 通过 Executors 自动创建的线程池((6种)。
1.Executors.newFixedThreadPool:创建⼀个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
(固定大小的线程池)
2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
(线程可缓存,并且线程数随着任务量而定)
3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
(单个线程的线程池)
4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
(可以执行延迟任务的线程池)
5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
(单线程可以执行延迟任务的线程池)
6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
(根据当前服务器的CPU创建线程池)
7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置,后⾯会详细讲。
(手动创建线程池)
4.1固定数量的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
4.1.1execute VS submit
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 创建一个固定大小的线程池
*/
public class ThreadPollDemo1 {
public static void main(String[] args) {
//1.创建一个包含5个线程的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
//2.使用线程池执行任务
//使用线程池执行任务二
for (int i = 0; i < 10; i++) {
//给线程池添加任务
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名称:" + Thread.currentThread().getName());
}
});
}
}
}
线程名称:pool-1-thread-2
线程名称:pool-1-thread-5
线程名称:pool-1-thread-5
线程名称:pool-1-thread-3
线程名称:pool-1-thread-4
线程名称:pool-1-thread-1
线程名称:pool-1-thread-4
线程名称:pool-1-thread-3
线程名称:pool-1-thread-5
线程名称:pool-1-thread-2
/**
* 演示有返回值的线程池
*/
import java.util.Random;
import java.util.concurrent.*;
public class ThreadPollDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
Future<Integer> result = threadPool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num = new Random().nextInt();
System.out.println("生成随机数:" + num);
return num;
}
});
System.out.println("得到线程池返回结果:" + result.get());
}
}
生成随机数:8578754
得到线程池返回结果:678934567878
得到结果:
向线程池中添加任务的方式有两种:
a. execute:只能执行不带返回值的任务;
b. submit:它可以执行有返回值的任务或者是没有返回值的任务
查看源码:
4.1.2线程工厂
作用:为线程池提供线程的创建。
提供的功能:
1.设置(线程池中的)线程命名规则
2.设置线程的优先级
3.设置线程分组
4.设置线程类型(用户线程和守护线程)
创建工厂类,实现ThreadFactory类。在创建线程池的时候把工厂类传进去
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import static java.lang.Thread.MAX_PRIORITY;
/**
* 线程工厂的示例
*/
public class ThreadPollDemo3 {
public static void main(String[] args) {
//1.创建线程工厂
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
//设置线程的命名规则
thread.setName("我的线程-" + r.hashCode());
//设置线程的优先级
thread.setPriority(MAX_PRIORITY);
return thread;
}
};
ExecutorService service = Executors.newFixedThreadPool(5,factory);
for (int i = 0; i < 5; i++) {
service.submit(() -> {
//任务
System.out.println("线程池开始执行了:" + Thread.currentThread().getName());
});
}
}
}
注意事项:
一定要将任务传入这个线程工厂,否则就会出错。
如果什么都不传,就会什么都打印不出来
4.2带缓存的线程池
ExecutorService threadPool =
Executors.newCachedThreadPool(threadFactory);
线程池会根据任务数创建线程池,并且在一定时间内可以重复使用这些线程。
示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 带缓存的线程池
*/
public class ThreadPollDemo4 {
public static void main(String[] args) {
//创建线程池
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
int finalI = i;
service.submit(() -> {
System.out.println("i: "+ finalI +"|线程名称:"+Thread.currentThread().getName());
});
}
}
}
有1000个任务并不会创建1000个线程,通过执行可以发现最多会创建100多个线程,然后进行复用。
4.3执行定时任务的线程池
4.3.1延迟执行(1次)
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 创建执行定时任务的线程池
*/
public class ThreadPollDemo5 {
public static void main(String[] args) {
//创建线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
System.out.println("添加任务的时间:" + LocalDateTime.now());
//执行定时任务(延迟3s)
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行了任务:"+ LocalDateTime.now());
}
},3, TimeUnit.SECONDS);
}
}
创建这个线程池时有3个参数:
a.执行任务
b.延迟 n秒执行
c.配合2执行的时间单位
4.3.2固定频率执行
可以让线程以以固定频率间隔n秒执行,创建这个线程池有四个参数:
a.执行任务
b.延迟n秒执行
c.执行定时任务的频率
d.配合3执行的时间单位
4.3.2.1scheduleAtFixedRate
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 创建执行定时任务的线程池
*/
public class ThreadPollDemo5 {
public static void main(String[] args) {
//创建线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
System.out.println("添加任务的时间:" + LocalDateTime.now());
///2s之后开始执行定时任务,定时任务每隔4s执行一次
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+LocalDateTime.now());
}
},2,4,TimeUnit.SECONDS);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/d77ce46b6d3822a7d2bb731e84c7d955.png)
4.3.2.2scheduleWithFixedDelay
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 创建执行定时任务的线程池
*/
public class ThreadPollDemo5 {
public static void main(String[] args) {
//创建线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
System.out.println("添加任务的时间:" + LocalDateTime.now());
//2s之后开始执行定时任务,定时任务每隔4s执行一次
service.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("执行时间:"+LocalDateTime.now());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},2,4,TimeUnit.SECONDS);
}
}
两个方法的区别:
scheduleAtFixedRate是以上一次任务开始的时间,加上任务的执行周期,作为下一次定时任务的开始时间。
scheduleWithFixedDelay是以上一次任务的结束时间,加上任务的执行周期,作为下次定时任务的开始时间。
所以,scheduleAtFixedRate执行任务为4s,而scheduleWithFixedDelay执行任务周期为5s
注意事项:
在scheduleAtFixedRate中,有时任务的执行时间大于延迟任务设定的时间间隔,那么当任务执行完之后才会开始执行下次任务,此时并不是以设定执行周期来执行任务。
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 创建执行定时任务的线程池
*/
public class ThreadPollDemo5 {
public static void main(String[] args) {
//创建线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
System.out.println("添加任务的时间:" + LocalDateTime.now());
//2s之后开始执行定时任务,定时任务每隔4s执行一次
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("执行任务:"+LocalDateTime.now());
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},2,4,TimeUnit.SECONDS);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/59f30311bbe7e2b1569f6bf72b8bd0d3.png)
4.4定时任务的单线程的线程池
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPollDemo6 {
public static void main(String[] args) {
//创建单线程线程池
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
System.out.println("添加任务的时间:" + LocalDateTime.now());
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("任务执行:" + LocalDateTime.now());
}
},2, TimeUnit.SECONDS);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/249d73bfec073731b20e37751a5e87a2.png)
4.5单线程线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollDemo7 {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程名" + Thread.currentThread().getName());
}
});
}
}
}
为什么不直接使用线程?
答:首先因为他是线程池,所有它能够提供任务队列和任务管理的功能,防止内存溢出。
其次它可以自定义拒绝策略(本文最后会讲到拒绝策略)
4.6根据当前CPU生成线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 根据当前设备的配置自动生成线程池
*/
public class ThreadPollDemo8 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newWorkStealingPool();
for (int i = 0; i < 100; i++) {
int finalI = i;
executorService.submit(() -> {
System.out.println("任务:" + finalI + "线程名:" + Thread.currentThread().getName());
});
}
while (!executorService.isTerminated()) {};
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/a0036b13c138f1ad16681f38084d15e6.png)
4.7 ThreadPoolExecutor
前六种创建线程池的方式是自动创建的,而这第7种方式是手动创建的,那为什么有了自动创建的方式却还需要手动创建呢?
答:那是因为在阿里手册里有强制规定,并且自动创建线程池有缺陷存在。
4.7.1参数说明
corePoolSize:核心线程数
maximumPoolSize:最大的线程数(包含核心线程数)
keepAliveTime:空闲线程的存活时间
TimeUnit unit:参数3的时间单位描述
BlockingQueue:任务队列,用于存储线程池的待执行任务的
ThreadFactory:线程工厂,用于生成线程
handler:拒绝管理器(处理极端问题,当任务队列都满了之后会采取的措施)
4.7.2具体实现
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPollDemo9 {
public static void main(String[] args) {
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
};
//手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(5,10,10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),factory,
new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
System.out.println("线程名称:"+Thread.currentThread().getName());
});
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/a6bc2b97b5980875cb615f0e183804b2.png)
4.7.3线程池的执行流程
关键执行步骤:
1.当任务来了之后,判断线程池中实际线程数是否小于核心线程数,如果小于就直接创建线程并执行任务。
2.当实际线程数大于核心线程数,它会判断任务队列是否已满,如果未满直接将任务存放队列即可。
3.判断线程池的实际线程数是否大于最大线程数,如果小于最大线程数直接创建线程执行任务;实际线程数已经等于最大线程数,那么会直接执行拒绝策略。
五、拒绝策略
1.AbortPolicy
提示异常拒绝执行,是默认的拒绝策略:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPollDemo9 {
public static void main(String[] args) {
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
};
//手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(2,2,10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2),factory,
new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 5; i++) {
executor.submit(() -> {
System.out.println("线程名称:"+Thread.currentThread().getName());
});
}
}
}
2.DiscardPolicy
忽略最新的任务,不报错:
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPollDemo9 {
public static void main(String[] args) {
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
};
//手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(2,2,10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2),factory,
new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 5; i++) {
int finalI = i;
executor.submit(() -> {
try {
Thread.sleep(100*finalI);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行任务:"+ finalI);
});
}
}
}
3.DiscardOldestPolicy
忽略旧任务(阻塞队列里面的第一个任务):
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPollDemo9 {
public static void main(String[] args) {
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
};
//手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(2,2,10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2),factory,
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 0; i < 5; i++) {
int finalI = i;
executor.submit(() -> {
try {
Thread.sleep(100*finalI);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行任务:"+ finalI);
});
}
}
}
4.CallerRunsPolicy
如果任务被拒绝了,则由调用线程(提交任务的线程)直接执行此任务。
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPollDemo9 {
public static void main(String[] args) {
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
};
//手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(2,2,10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2),factory,
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 5; i++) {
int finalI = i;
executor.submit(() -> {
try {
Thread.sleep(100*finalI);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--执行任务:"+ finalI);
});
}
}
}
5.自定义拒绝策略
import java.util.concurrent.*;
public class ThreadPollDemo9 {
public static void main(String[] args) {
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
};
//手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(2, 2, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(2), factory,
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//自定义的拒绝策略
System.out.println("我执行了自定义的拒绝策略");
}
});
for (int i = 0; i < 5; i++) {
int finalI = i;
executor.submit(() -> {
try {
Thread.sleep(100*finalI);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--执行任务:"+ finalI);
});
}
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/a65bcc55e49f820c489368df2e25cef4.png)
六、线程池的状态
分为五种:
1.RUNNING:线程池创建之后的状态,这种状态下可以执行任务;
2.SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行结束;
3.STOP:该状态下线程池不再接受新任务,不会处理工作队列中的任务,并且会中断线程;
4.TIDYING:该状态下所有任务都已终止,将会执行terminated方法;
5.TEIMINATED:执行完terminated方法之后。
Shutdown VS ShutdownNow:
1.shutdown执行时线程池终止接收新任务,并且会将任务队列中的任务处理完;
2.shutdoNow执行时线程池终止接收新任务,并且会终止执行任务队列中的任务。
七、线程数设置
ref 多线程线程数设置多少合适_多线程数量多少合适-CSDN博客
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
备注:这个公式也是前辈们分享的,当然之前看了淘宝前台系统优化实践的文章,和上面的公式很类似,不过在CPU数目那边,他们更细化了,上面的公式只是参考。不过不管什么公式,最终还是在生产环境中运行后,再优化调整。
我们继续上面的任务,我们的服务器CPU核数为4核,一个任务线程cpu耗时为20ms,线程等待(网络IO、磁盘IO)耗时80ms,那最佳线程数目:( 80 + 20 )/20 * 4 = 20。也就是设置20个线程数最佳。
从这个公式上面我们就得出,线程的等待时间越大,线程数就要设置越大,这个正好符合我们上面的分析,可提升CPU利用率。那从另一个角度上面说,线程数设置多大,是根据我们自身的业务的,需要自己去压力测试,设置一个合理的数值。
基础常规标准
在确认了核心数后,再去判断是 CPU 密集型任务还是 IO 密集型任务:
CPU 密集型任务:
比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。
操作内存处理的业务,一般线程数设置为:CPU核数 + 1 或者 CPU核数*2。核数为4的话,一般设置 5 或 8。
如果设置过多的线程数,实际上并不会起到很好的效果。此时假设我们设置的线程数量是 CPU 核心数的 2 倍,因为计算任务非常重,会占用大量的 CPU 资源,所以这时 CPU 的每个核心工作基本都是满负荷的,而我们又设置了过多的线程,每个线程都想去利用 CPU 资源来执行自己的任务,这就会造成不必要的上下文切换,此时线程数的增多并没有让性能提升,反而由于线程数量过多会导致性能下降。
因此,对于 CPU 密集型的计算场景,理论上线程的数量 = CPU 核数就是最合适的,不过通常把线程的数量设置为CPU 核数 +1,会实现最优的利用率。即使当密集型的线程由于偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程也能确保 CPU 的时钟周期不会被浪费,从而保证 CPU 的利用率。
IO 密集型任务:
比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。
文件操作,网络操作,数据库操作,一般线程设置为:cpu核数 / (1-0.9),核数为4的话,一般设置 40。
线程数 = CPU 核心数 * (1 + IO 耗时/ CPU 耗时)
通过这个公式,我们可以计算出一个合理的线程数量,如果任务的平均等待时间长,线程数就随之增加,而如果平均工作时间长,也就是对于我们上面的 CPU 密集型任务,线程数就随之减少。可以采用 APM 工具统计到每个方法的耗时,便于计算 IO 耗时和 CPU 耗时。
对于 IO 密集型任务最大线程数一般会大于 CPU 核心数很多倍,因为 IO 读写速度相比于 CPU 的速度而言是比较慢的,如果我们设置过少的线程数,就可能导致 CPU 资源的浪费。而如果我们设置更多的线程数,那么当一部分线程正在等待 IO 的时候,它们此时并不需要 CPU 来计算,那么另外的线程便可以利用 CPU 去执行其他的任务,互不影响,这样的话在任务队列中等待的任务就会减少,可以更好地利用资源。