目录
🌟一、线程池
🌈1、线程池是什么?
🌈2、为什么要使用线程池?
🌈3、怎么使用线程池?
1、使用标准库中的线程池(6种)——>不推荐使用。
2、自定义一个线程池(重点)
🌟一、线程池
🌈1、线程池是什么?
简单理解就是:在线程池中已经存在了一些创建好的线程,只需要往线程池中提交任务即可,当任务被提交到线程池之后,任务就会被自动执行。当程序启动的时候,如果发现有任务就立刻执行,没有任务就阻塞等待。
🌈2、为什么要使用线程池?
线程池最大的好处就是减少频繁创建和销毁线程的系统的开销,从而提高效率。
🌈3、怎么使用线程池?
1、使用标准库中的线程池(6种)——>不推荐使用。
(1)6种方法
public static void main(String[] args) {
// 1. 用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将收回并移出缓存
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建一个操作无界队列且固定大小线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 3. 创建一个操作无界队列且只有一个工作线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 4. 创建一个单线程执行器,可以在给定时间后执行或定期执行。
ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
// 5. 创建一个指定大小的线程池,可以在给定时间后执行或定期执行。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
// 6. 创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务,不保证处理顺序
Executors.newWorkStealingPool();
}
❓面试题1:如何自定义线程池? 创建线程池时构造方法的参数及含义?(重点)
(1) 通过工厂方法获取的线程池,最终都是ThreadPoolExecutor对象。通过创建(new)一个 ThreadPoolExecutor的对象自定义线程池。
(2)参数含义
1️⃣corePoolSize:核心线程数,创建线程池时包含的最小线程数量。(一次性全部创建完成)
2️⃣maxminPoolSize:最大线程数。当线程不够用时,允许系统可以创建的最多线程数(最大线程数-核心线程数)。
3️⃣keepAliveTime:临时线程空闲的时长。
4️⃣Timeunit unit:空闲的时间单位和keepAliveTime一起使用。
5️⃣BlockingQueue:存放任务的阻塞队列。
6️⃣threadfactory:线程工厂,规定了怎么去创建线程。用系统默认的就行。
7️⃣RejectedExecutionHander hander:拒绝策略,触发时机:当线程池处理不了过多的任务时。
理解上述过程:
🌰1:吃火锅~
(1)火锅店一共有10张桌子(对应核心线程数);
(2)中午2.00去,店里没有人,随便坐,此时店里的桌子足够招待客人;(对应:当任务量较小时,核心线程完全可以处理)
(3)到了下午5/6点,到饭点了,来吃饭的人逐渐增多,此时10张桌子就已经坐满了(对应:核心线程数已满);
(4)再来的顾客就要排号(对应:往阻塞队列里面添加任务)
(5)当顾客人数越来越多,老板就在店门口加了5张桌子(对应:创建的临时线程数)
(6)当顾客慢慢吃完了,也没有新来的排号的顾客时,门口的5张桌子就空闲下来了,说明店里的10张桌子完全可以处理顾客的人数;
(7)当老板等待30min后(对应:线程的空间时间和时间单位)仍然没有新来的顾客,门口的桌子用不上了,那就可以收回了(对应:回收临时线程)
(8)当在用餐高峰期,门口的桌子也用完了,那对于新来的顾客执行拒绝策略。
上述过程总结:
❓面试题2:描述一下线程池的工作原理?(上述7个参数是如何搭配使用的?)(重点)
(1)当任务添加到线程池中,先判断当前任务数是否大于核心线程数;
(2)如果任务数小于等于核心线程数,则直接执行任务,否则加入阻塞队列中等待;
(3)当阻塞队列满了之后,按照指定的最大线程数创建临时线程(最大线程数-核心线程数);
(4)当阻塞队列满了之后,而且临时线程也已经创建完成,再次提交任务的时候,就会执行拒绝策略
(5)当任务量减少且核心线程数完全够执行,临时线程达到一定的空间时长之后就会被回收。
理解拒绝策略:
❓问题3:为什么不推荐使用系统自带的线程池?
❓ 面试题3:创建线程池的时候,指定的核心线程数一般是多少比较合适?
(1)这个没有一个准确的答案,要根据业务场景和计算机配置来决定;
(2)对于计算密集型的程序,那么线程数可以适当的增大;对于IO密集型的程序,取决于磁盘的读写效率,线程数过大也不会提高程序的效率;
(3)还需要考虑COU的核心数量是多少;
(4)最终要通过测试对比,来确定一个合适的线程数。
(2)演示使用JDK提供的方法实现线程池
(1)创建线程
(2)提交线程
threadPool.submit();
//模拟实现线程池:一共有10个任务,线程池有3个线程。将任务提交到线程池,期望输出结果:每次这10个线程都是由线程池中的三个线程执行的。
public static void main(String[] args) throws InterruptedException {
//1、创建一个大小为3的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
//2、提交任务到线程池
for (int i = 0; i < 10; i++) {
int taskId = i;
//3、表示要执行的任务
threadPool.submit(()->{
System.out.println("正在执行任务"+taskId+","+Thread.currentThread().getName());
});
}
//4、等待任务执行
TimeUnit.SECONDS.sleep(5);
System.out.println("任务执行结束");
}
2、自定义一个线程池(重点)
🍀 创建一个线程池需要满足的条件:
(1)需要提交任务到线程池,那么就要有一种数据结构来保存我们提交的任务;(考虑用阻塞队列实现)
(2)创建线程时需要指定初始线程数量,这些线程不停的扫描阻塞队列,一旦有任务就立刻执行。(可以考虑用线程池对象的构造方法,接收要创建线程的数据,并在构造方法中完成线程的创建)
自定义实现线程池:
public class a03_MyThreadPool {
//目标:用阻塞队列模拟线程池(创建一些线程表示线程池中已有的工作线程),往线程池中提交任务后,任务被工作线程执行
//1、定义一个阻塞队列(就代表线程池),初始化容量为3。--------不理解。注意类型为Runnable
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(3);
//2、核心方法:对外提供一个方法,用来往线程池中提交任务
public void submit(Runnable task) throws InterruptedException {
queue.put(task);
}
//3、构造方法中,就要创建一些工作线程,让这些工作俩执行上述提交的任务
public a03_MyThreadPool(int capacity){
if(capacity <= 0){
throw new RuntimeException("线程池的数量不能小于0");
}
//4、在线程池中创建一些工作线程(创建的个数就是初始化线程池给定的容量数)
for (int i = 0; i < capacity; i++) {
Thread thread= new Thread(()->{
//5、不停的扫描
while (true){
try {
//6、取出提交的任务,并执行
Runnable task = queue.take();
task.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//7、启动线程
thread.start();
}
}
}
测试类:
public static void main(String[] args) throws InterruptedException {
//1、创建一个大小为3的线程池
a03_MyThreadPool threadPool = new a03_MyThreadPool(3);
//2、提交任务到线程池
for (int i = 0; i < 10; i++) {
int taskId = i;
//3、表示要执行的任务
threadPool.submit(()->{
System.out.println("正在执行任务"+taskId+","+Thread.currentThread().getName());
});
}
//4、等待任务执行
TimeUnit.SECONDS.sleep(5);
System.out.println("任务执行结束");
}
执行结果:
不要焦虑,行动解决焦虑!