线程池 1.缺点
缺点一:使用时创建线程
class MyThreadextends Thread(
@Override
public void run() {
// 多线程执行的一些代码
}
缺点2:线程用完后消失
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
以前我们需要线程的时候,就去创建对象,对象用完了,线程就消失了,这样会浪费操作系统的资源。
为了解决上面的缺点,我们引入了线程池,它其实就是一个存放线程的容器
2.导入
一开始,线程池是空的。 当我们把任务提交给线程池时,线程池本身会自动创建一个线程,任务会带着线程去完成任务
特例:当任务1还没有执行完,而任务2已经提交到线程池,此时线程池会继续创建新的线程对象,让任务2可以使用新的线程执行任务
注意:线程池中线程创建线程是有上限的,这个上限可以自己设置
3. 核心原理是创建池。 当池为空并提交任务时,池将创建一个新的线程对象。 任务执行完毕后,线程会归还池中。 下次再次提交任务时池本,无需新建线程,直接复用即可。 现有线程还好,但是如果提交任务时池中没有空闲线程,无法创建新线程,任务就会排队等待 4.创建线程池
注意:线程池一般不会关闭。 服务器每天 24 小时运行。 如果服务器没有关闭,随时可能有任务,线程池是不会关闭的。
Executors:线程池的工具类,通过调用方法返回不同类型的线程池对象
方法名称说明
公共静态 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();
}
}
我的可运行类:
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线程多路复用demo:
测试类:
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();
}
}
我的可运行类:
public class MyRunnable implements Runnable {
@Override
public void run() {
//for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->");
//}
}
}
运行结果:
由于线程在执行下一个任务前会休眠几秒,之前的任务已经执行完毕,后续的任务会一直使用第一个线程
4.3 创建capped线程池
测试类:
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();
}
}
我的可运行类:
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,上面的测试类中创建了4个任务。 可以看到创建了有限数量的线程池
5.自定义线程池
通过上面的案例分析可以看出,使用Executors工具类创建线程池虽然方便,但是不够灵活。当提交的任务很多时,任务的执行需要排队,长度无法定义团队。 这时候我们可以自己创建一个线程池对象来修改我们要修改的参数。
使用Executors类创建线程池对象的方法源码如下,共七个参数
借助API帮助文档,查看ThreadPoolExecutor构造方法的参数说明
参数说明类型
核心池大小
核心线程数(不能小于0)
整数
最大池大小
线程池最大线程数(最大数>=核心线程数)
整数
保活时间
空闲时间(值)(不能小于0)
长的
单元
空闲时间(单位)(用 TimeUnitt 指定)
时间单位
工作队列
阻塞队列(不能为空)
阻塞队列
线程工厂
创建线程的方式(不能为null)
线程工厂
处理程序
执行任务过多时的解决方法(不能为null)
拒绝执行处理程序
5.1 案例一
提交任务量=核心线程数
有一个现有的线程池,并且存在以下规定,
结果:线程池创建三个线程分别处理这三个任务
5.2 案例二
提交任务量>核心线程数
有一个现有的线程池,并且存在以下规定,
结果:由于核心线程数为3,所以只能分配3个线程完成其中3个任务,剩下2个任务会放到阻塞队列中排队
5.3 案例三
提交任务量>核心线程数+阻塞队列长度
有一个现有的线程池,并且存在以下规定,
结果:一共提交了八个任务。 线程池先创建三个线程处理其中三个任务,然后将多出的三个线程放入阻塞队列中排队。 发现多了两个线程,线程池会重新创建。 两个临时线程处理剩下的两个任务
注意:任务的执行顺序不一定按照提交的先后顺序执行(先到服务队列)
临时线程创建条件:
5.3 情况四
提交任务量>核心线程数+阻塞队列长度+临时线程数
有一个现有的线程池,并且存在以下规定,
结果:移除分配的3个核心线程、3个临时线程和阻塞队列中的3个线程后,剩下的任务会触发任务拒绝策略池本,即abandon
任务拒绝策略说明
线程池执行器。 中止策略
默认策略:丢弃任务并抛出 RejectedExecutionException
线程池执行器。 丢弃策略
丢弃任务而不抛出异常。 不建议这样做
线程池执行器。 丢弃最旧策略
放弃队列中等待时间最长的任务,将当前任务加入队列
ThreadPoolExecutor.CallerRunsPolicv
调用任务的run()方法绕过线程池直接执行
任务解析策略是ThreadPoolExecutor中的一个内部类。 单独存在没有意义,需要依附于ThreadPoolExecutor,所以设置为内部类
创建静态内部类:new outer class。 内部类()
只要理解上面的任务拒绝策略
ThreadPoolExecutor.DiscardoldestPolicy 丢弃队列中的第一个任务。 在情况 4 中,任务 4 将被丢弃
// 自定义线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, // /核心线程数量,能小于0
6, // 最大线程数,不能小于0,最大数量 >= 核心线程数量
60, // 空闲线程最大存活时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3), // 任务队列
Executors.defaultThreadFactory(), // 创建线程工厂
new ThreadPoolExecutor.AbortPolicy() // 任务的拒绝策略
);
概括:
1.创建一个空池 2.提交任务时,线程池会创建一个线程来执行任务,执行完返回线程
不断提交任务,会有以下三个临界点