线程池
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理(..)
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
使用线程池
- JDK 5.0起提供了线程池相关API: ExecutorService和Executors
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command)∶执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般用来执行Callable - void shutdown():关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
示例:我们使用Runnable来实现线程,并结合线程池来进行操作
public class ThreadPoll {
public static void main(String[] args) {
// 1. 创建服务,擦行间线程池
// newFixedThreadPool(线程池大小)
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭连接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
结果
pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
pool-1-thread-6
创建线程池的3大方法
- Executors.newSingleThreadExecutor(); 创建单个线程
- Executors.newFixedThreadPool(5); 创建一个固定的线程池的大小。
- Executors.newCachedThreadPool(); 缓存(可伸缩的)的线程池【遇强则强,遇弱则弱】
示例一 单个线程的线程池
public static void main(String args[]){
ExecutorService threadPool = Executors.newSingleThreadExecutor();
try {
for (int i = 0; i < 5; i++) {
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//线程池用完必须要关闭线程池
threadPool.shutdown();
}
}
结果
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
示例二 固定长度的线程池
public static void main(String args[]){
ExecutorService threadPool = Executors.newFixedThreadPool(5);
try {
for (int i = 0; i < 5; i++) {
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//线程池用完必须要关闭线程池
threadPool.shutdown();
}
}
结果
pool-1-thread-1 ok
pool-1-thread-2 ok
pool-1-thread-3 ok
pool-1-thread-4 ok
pool-1-thread-5 ok
示例三 缓存线程池
public static void main(String args[]){
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 5; i++) {
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//线程池用完必须要关闭线程池
threadPool.shutdown();
}
}
结果: 如果for循环运行100次,不一定产生100条线程。这里之所以产生5条数据是因为cpu速率较快的原因。
pool-1-thread-1 ok
pool-1-thread-2 ok
pool-1-thread-4 ok
pool-1-thread-5 ok
pool-1-thread-3 ok
改变for循环的循环次数,多运行几次,你就可以体会缓存线程池”遇强则强,遇弱则弱“的含义。
线程池的7大参数
为什么要了解线程池的7大参数去自己定义线程池?我们来看看阿里巴巴规范就明白了。
我们来看看Executors创建线程池的源码
- 第一个参数:核心线程数
- 第二个参数:最大线程数
- 第三个参数:活着的时间(如果超过这个时间,没有人调用就会被释放)
- 第四个参数:第三个参数的时间单位
- 第五个参数:阻塞队列
- 第六个参数:创建线程的工厂,该参数一般不会改动
- 第七个参数:拒绝策略
//单例线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
//缓存线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//通过上面的三种类型的线程池我们发现实际他们都掉眼泪ThreadPoolExecutor
//接下来我们来看看ThreadPoolExecutor的源码
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大的线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂 创建线程的 一般不用动
RejectedExecutionHandler handler //拒绝策略
)
接下来我们举例一个银行业务来说明这些函数的具体含义
- 首先有银行有5个柜台可以办理业务,这个5我们可以理解我最大线程数。
- 平时银行只开放2个柜台来办理业务,这个2就表示核心线程。
- 开放2个柜台办理业务,其他还没开始办理业务的人,在候客区进行等待(假设候客区有3个位置)。这个候客区就是阻塞队列。
- 如果前两个柜台和候客区人都满了就会通知打开其它的柜台(开启其他的线程).
- 如果所有客服业务都办理得差不多了,2,3,5在一定的等待时间内(超时等待)就会关闭(释放)。
自定义线程池
我们首先用默认的拒绝策略来写一个线程池,点击defaultHandler查看默认的拒绝策略。
理解默认决绝策略:当银行满了(柜台和候客区都满了)就不处理满了之后的人的业务,并且会抛出异常
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
示例:
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MyThreadPoolExample01 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 1; i < 2; i++) {
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
}
结果:改变for循环中i的判断条件进行多次测试的结果如下
//当for循环中i<2的时候结果如下
pool-1-thread-1 ok
//当for循环中i<=5的时候结果如下 核心线程数(平时开发的柜台)2+阻塞区(候客区)3=5 还是只使用两个柜台办理
pool-1-thread-1 ok
pool-1-thread-2 ok
pool-1-thread-1 ok
pool-1-thread-2 ok
pool-1-thread-1 ok
//当for循环中i<=2+3+3=8的时候就会触发最大线程池数。(开启其它柜台)
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-2 ok
pool-1-thread-3 ok
pool-1-thread-4 ok
//如果i>8(i<=9)的时候就会因为使用了默认的拒绝策略而抛出异常
java.util.concurrent.RejectedExecutionException:
4大拒绝策略
- new ThreadPoolExecutor.AbortPolicy;该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常
- new ThreadPoolExecutor.CallerRunsPolicy();该拒绝策略为:哪来的去哪里, main线程进行处理
- new ThreadPoolExecutor.DiscardPolicy();该拒绝策略为:队列满了,丢掉其它多的人,不会抛出异常。
- new ThreadPoolExecutor.DiscardOldestPolicy(): 该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常
将上面自定义的线程池的决绝策略改为CallerRunsPolicy,for 中i<11,运行结果如下
pool-1-thread-1 ok
main ok
main ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-2 ok
pool-1-thread-4 ok
pool-1-thread-3 ok
pool-1-thread-5 ok
将上面自定义的线程池的决绝策略改为DiscardPolicy,for 中i<11,运行结果如下(发现最多只执行8次)
pool-1-thread-1 ok
pool-1-thread-2 ok
pool-1-thread-2 ok
pool-1-thread-2 ok
pool-1-thread-1 ok
pool-1-thread-3 ok
pool-1-thread-4 ok
pool-1-thread-5 ok
将上面自定义的线程池的决绝策略改为DiscardOldestPolicy,for 中i<11,运行结果如下
pool-1-thread-2 ok
pool-1-thread-2 ok
pool-1-thread-2 ok
pool-1-thread-2 ok
pool-1-thread-1 ok
pool-1-thread-1 ok
pool-1-thread-2 ok
pool-1-thread-3 ok
pool-1-thread-4 ok
pool-1-thread-5 ok
如何正确的设置线程池的最大线程数?
分两种情况考虑