前言
本篇通过介绍要用线程池的原因,Java标准库中提供的创建线程池的方法,了解ThreadPoolExecutor对象,实现线程池等,如有错误,请在评论区指正,让我们一起交流,共同进步!
本文开始
1. 为什么要有线程池?
1.1 线程池的目的:为了提高效率
线程的创建比进程轻量,但是在频繁创建的情况下,它的开销也是不容忽视的!
所以就有了两种方式进一步 提高效率:
① 协程:是轻量级线程,但是java标准库不支持;
② 线程池:从池中取线程,是用户态操作 - 节省时间;
从系统创建线程,创建是要在内核态中完成 - 比较耗费时间;
这里你可能比较疑惑,什么是用户态和内核态?接下来为小伙伴介绍一下!
1.2 了解内核,需要知道一些操作系统的概念;
操作系统 = 内核 + 配套的应用程序
什么是内核?
内核: 操作系统最核心的功能模块集合; 包含各种驱动,进程管理,内存管理等操作;
内核的工作介绍:
内核需要给上层应用程序提供支持,同一时刻,可能有多个应用程序,而内核只有一个,内核需要给这么多程序提供服务,可能有些服务提供的就不会及时;
① 用户态:用户自己操作,时间可以控制
② 内核态:内核处理服务操作,时间就不可控了
2. 标准库中提供的线程池
直接调用Java标准库实现线程池操作:
代码实现:
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadDemo3 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);
//添加任务
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("run");
}
});
}
}
这里为什么不是直接new ExecutorService对象呢?
这里通过 Executors 这个类中的静态方法 完成对象的构造;这里就涉及了工厂模式!
2.1 那什么是工厂模式呢?
工厂模式:创建对象,不再使用new, 使用一些其他方法,(一般指静态方法),协助我们把对象创建出来;
工厂模式的作用:
使用构造方法构造对象,会产生Bug;而工厂模式就是填构造方法的坑的;
构造方法产生的局限性:
使用构造方法构造对象的方式,就得基于重置,此时遇到方法名一样的就会产生Bug; -> 这就是构造方法的局限性;
2.2
线程池的创建,其实是 “ThreadPoolExecutor” 是原本的线程池对象,而Executors.newFixedThreadPool()等工厂方法是对这个对象的封装;想了解创建线程池的方法;可以查阅官方文档;
查阅官方文档:认识ThreadPoolExecutor对象构造方法的参数;
参数介绍:
① int corePoolSize: 核心线程数;
把核心线程称为正式员工,一般不会裁掉的员工;
② int maximumPoolSize: 最大线程数;
把最大线程数认为是 正式员工 + 临时员工;(临时员工可能会被裁掉)
当任务多的时候,线程池会多创建一些 “临时线程” (临时员工);当任务少的时候,线程池就会把多余的临时线程销毁掉;
③ long keepAliveTime: 临时线程保持存活的时间; (临时工不被裁掉的时间)
④ TimeUnit unit: 描述时间的单位s,ms,分钟等;
⑤ BlockingQueue< Runnable > workQueue: 阻塞队列管理任务;
【注】手动给线程池指定一个阻塞队列,方便控制/获取队列中的信息;
⑥ ThreadFactory threadFactory: 工厂模式,创建线程的辅助类;
⑦ RejectedExecutionHandler handler: 线程池的拒绝策略;
描述:线程池满了,继续添加任务,如何拒绝!!
标准库中提供的四种拒绝策略:
前提条件:线程池已满,添加一个新的任务;
1.ThreadPoolExecutor.AbortPolicy : 任务满了,继续添加任务,添加操作直接抛出异常;(新老任务都不执行)
2.ThreadPoolExecutor.CallerRunsPolicy:让添加的线程自己负责执行这个添加的任务;
3.ThreadPoolExecutor.DiscardOldestPolicy :丢弃最老的任务;(丢弃老任务,执行新任务)
【注】最老的任务:阻塞队列里的队首任务;(最先安排的任务)
4.ThreadPoolExecutor.DiscardPolicy:丢弃添加的新任务;
3. 模拟实现线程池
3.1 创建线程池
① 线程池底部使用阻塞队列BlockingDeque ,来存放任务!
代码实现:
//底层是阻塞队列,用来存放任务
private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();
② 使用sumit(): 把任务添加到阻塞队列中;
像生产者一样,每次有任务都把任务都添加到阻塞队列中
代码实现:
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
③ 构造带一个参数的构造方法: 实现一个固定线程数的线程池; 像消费者,不停取任务并执行;
为了不停取任务,使用循环,保证有任务就取出执行;
代码实现:
public MyThreadPool(int n) {
//循环中创建n个线程
for (int i = 0; i < n; i++) {
Thread t = new Thread( () -> {
//线程中做的事情/任务
try {
//循环有任务就取任务执行 =》 循环取
while (true) {
Runnable runnable = queue.take();
runnable.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();//启动
}
}
④ 使用线程池
创建线程池,循环加载1000个任务,并打印;
public static void main(String[] args) throws InterruptedException {
//创建线程池
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int number = i;//每次创建number相当于没有改变number这个变量
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("run " + number);
}
});
}
}
问题1:为什么要创建number变量?
submit第一个参数是匿名内部类,匿名内部类的变量捕获,final修饰或者代码不被修改的变量;
使用 i 变量打印,i 这个变量一直在变,无法捕获;所以需要重新创建一个变量;
问题2:创建多大数量的线程池合适呢?
这里就需要测试,测出一个效率合适,占用资源和的线程池;=》测出合适的数量即可;
4. 线程池总代码
代码如下:
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
class MyThreadPool {
//底层是阻塞队列,用来存放任务
private BlockingDeque<Runnable> queue = new LinkedBlockingDeque<>();
//使用submit,把任务添加到阻塞队列中
//像生产者,一样把任务添加到阻塞队列中
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
//带一个参数的构造方法:实现一个固定线程数的线程池
public MyThreadPool(int n) {
//循环中创建n个线程
for (int i = 0; i < n; i++) {
//像消费者,不停取任务执行
Thread t = new Thread( () -> {
//线程中做的事情/任务
try {
//循环有任务就取任务执行 =》 循环取
while (true) {
Runnable runnable = queue.take();
runnable.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();//启动
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
//创建线程池
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
//匿名内部类的变量捕获,final修饰或者代码不被修改的变量
int number = i;//每次创建number相当于没有改变number这个变量
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("run " + number);
}
});
}
}
}
总结
✨✨✨各位读友,本篇分享到内容如果对你有帮助给个👍赞鼓励一下吧!!
感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!!