线程池
1. 弊端
弊端一:用到线程的时候就创建
class MyThreadextends Thread(
@Override
public void run() {
// 多线程执行的一些代码
}
弊端二:用完之后线程消失
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
之前我们需要线程的时候就会创建对象,而用完之后,线程就会消失,如此会浪费操作系统的资源
为解决以上弊端,我们引入线程池,线程池实际上就是一个存储线程的一个容器
2. 引入
刚开始,线程池里面是空的,当我们把任务提交给线程池时,线程池本身就会自动创建一个线程,任务一就会拿着线程去完成任务
特殊情况:当任务一还未执行完成,而任务二已经被提交到线程池,此时,线程池就会继续创建新的线程对象,任务二从而可以拿着新的线程去执行任务
注:线程池的线程创建线程是有上限的,而这个上限可以自己设置
3. 核心原理
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
4. 创建线程池
-
创建线程池
-
提交任务
-
所有的任务全部执行完毕,关闭线程池
注:线程池一般不会关闭,服务器都是二十四小时运行,服务器不关闭,任务就随时都可能有,线程池就不会关闭
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 创建一个没有上限的线程池 |
public staticExecutorService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
这里的“没有上限”指的并不是真正意义上的没有上限,其最大值为int类型的最大值(21多个亿),但不需要考虑线程不够用的情况,线程池还没创建这么多线程时,电脑就已经崩溃了
4.1 创建没有上限的线程池
测试类:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class test1 {
public static void main(String[] args) {
// 获取线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
// 提交任务(多个任务)
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
// 销毁线程池
pool.shutdown();
}
}
MyRunnable类:
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
运行结果:
由此可见,线程池所创建了四个线程对象去执行任务
4.2 线程的复用演示:
测试类:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class test1 {
public static void main(String[] args) throws InterruptedException {
// 获取线程池对象
ExecutorService pool = Executors.newCachedThreadPool();
// 提交任务(多个任务)
pool.submit(new MyRunnable());
// 让上一个任务完成任务将线程还给线程池
// 结果就是一直使用线程一去执行任务
Thread.sleep(1000);
pool.submit(new MyRunnable());
Thread.sleep(1000);
pool.submit(new MyRunnable());
Thread.sleep(1000);
pool.submit(new MyRunnable());
// 销毁线程池
pool.shutdown();
}
}
MyRunnable类:
public class MyRunnable implements Runnable {
@Override
public void run() {
//for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->");
//}
}
}
运行结果:
由于在执行下一个任务之前,让线程先睡几秒导致之前的任务已经执行完毕,之后的任务就会一直使用第一个线程
4.3 创建有上限的线程池
测试类:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class test1 {
public static void main(String[] args) {
// 获取线程池对象
ExecutorService pool = Executors.newFixedThreadPool(3);
// 提交任务(多个任务)
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
// 销毁线程池
pool.shutdown();
}
}
MyRunnable类:
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
运行结果:
运行结果中只有线程1、2、3,上面测试类中创建了四个任务,由此可见,创建了有限个线程池
5. 自定义线程池
分析上面案例可知,利用Executors工具类创建线程池虽然方便,但不够灵活。当提交的任务较多时,任务的执行需要排队,无法定义队伍的长度,这时我们可以创建线程池对象,从而自行修改自己想要修改的参数
利用Executors类创建线程池对象的方法源码如下,共有七个参数
借助API帮助文档,查看ThreadPoolExecutor的构造方法的参数说明
参数 | 说明 | 类型 |
---|---|---|
corePoolSize | 核心线程数量(不能小于0) | int |
maximumPoolSize | 线程池中最大线程的数量(最大数量 >= 核心线程数量) | int |
keepAliveTime | 空闲时间(值) (不能小于0) | long |
unit | 空闲时间(单位) (用TimeUnitt指定) | TimeUnit |
workQueue | 阻塞队列(不能为null) | BlockingQueue |
threadFactory | 创建线程的方式(不能为null) | ThreadFactory |
handler | 要执行的任务过多时的解决方案(不能为null) | RejectedExecutionHandler |
5.1 情况一
提交任务量 = 核心线程数
现有一个线程池,现有如下规定,
- 核心线程(corePoolSize):3
- 临时线程:3
- 提交 3 个任务
结果:线程池创建三个线程,分别去处理这三个任务
5.2 情况二
提交任务量 > 核心线程数
现有一个线程池,现有如下规定,
- 核心线程(corePoolSize):3
- 临时线程:3
- 提交 5 个任务
结果:由于核心线程数为 3,只能分配三个线程去完成其中的三个任务,剩余的两个任务则会被放到阻塞队列中进行排队
5.3 情况三
提交任务量 > 核心线程数 + 阻塞队列长度
现有一个线程池,现有如下规定,
- 核心线程(corePoolSize):3
- 临时线程:3
- 阻塞队列长度:3
- 提交 8 个任务
结果:共提交了八个任务,线程池先创建三个线程处理其中三个任务,再将多余的三个线程放入阻塞队列中进行排队,发现还多出两个线程,线程池会再创建两个临时线程处理给剩下两个任务
注:任务的执行顺序不一定按照提交的顺序执行(先去服务队列外的)
临时线程创建的条件:
- 核心线程都在忙
- 阻塞队列已经排满了任务
5.3 情况四
提交任务量 > 核心线程数 + 阻塞队列长度 + 临时线程数
现有一个线程池,现有如下规定,
- 核心线程(corePoolSize):3
- 临时线程:3
- 阻塞队列长度:3
- 提交 10 个任务
结果:除去已经分配的三个核心线程,三个临时线程以及阻塞队列中的三个线程,剩余的任务则会触发任务拒绝策略,也就是舍弃不要
任务拒绝策略 | 说明 |
---|---|
ThreadPoolExecutor.AbortPolicy | 默认策略:丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常这是不推荐的做法 |
ThreadPoolExecutor.DiscardoldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicv | 调用任务的run()方法绕过线程池直接执行 |
任务解决策略是ThreadPoolExecutor中的内部类,其单独存在无意义,需依附于ThreadPoolExecutor,因此被设置为内部类
创建静态内部类:new 外部类.内部类()
以上任务拒绝策略了解即可
ThreadPoolExecutor.DiscardoldestPolicy 抛弃队列中排在第一个的任务,情况四中,则会抛弃任务四
// 自定义线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // /核心线程数量,能小于0
6, // 最大线程数,不能小于0,最大数量 >= 核心线程数量
60, // 空闲线程最大存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 创建线程工厂
new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
);
小结:
- 1.创建一个空的池子
- 2.有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程
不断的提交任务,会有以下三个临界点
- 当核心线程满时,再提交任务就会排队
- 当核心线程满,队伍满时,会创建临时线程
- 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略