🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!欢迎志同道合的朋友一起加油喔🦾🦾🦾
目录
1. 线程池的概念
线程池:一个容纳多个线程的容器,容器中的线程可以重复使用,省去了频繁创建和销毁线程对象的操作。
线程池作用:
- 降低资源消耗,减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度,当任务到达时,如果有线程可以直接用,不会出现系统僵死。
- 提高线程的可管理性,如果无限制的创建线程,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的核心思想:把线程创建好了之后,放到池子里,需要使用线程,就直接从池子里取,而不是通过系统来创建。当线程用完了,又放回池子里,而不是通过系统来销毁!!(线程复用,同一个线程可以被重复使用,来处理多个任务。)
为什么从线程池取线程要比从系统申请效率更高呢
因为从线程池取线程是纯粹的用户态操作
从系统创建线程,涉及到内核态和用户态的切换
内核态用户态是什么?
内核态:内核态是操作系统运行的状态,也叫做超级用户模式,此时的操作系统可以直接访问整个硬件,包括内存、CPU、外围设备等,执行系统级别的指令,如I/O操作、创建或结束一个进程等。
用户态:用户态是用户进程运行的状态,权限相对较低,用户进程只能访问自身的地址空间,无法直接访问内核数据和硬件设备,只能通过系统调用的方式,向操作系统请求服务,间接地进行一些系统级别的操作。
这种模式的设计是为了保护操作系统和硬件免受恶意或者错误的用户进程操作。如果用户进程出现错误,只会影响到该进程本身的地址空间,不会对操作系统和其他进程产生影响。
🍬举例形象说明线程池
设想一个快递公司,它有一些正式员工和临时工,还有一个用于存放待投递快递的仓库:
"正式员工"就像是线程池中的核心线程,他们始终在公司里,即使没有快递需要送,他们也会待在公司,不会被解雇。这就像线程池中的核心线程,即使没有任务,他们也会一直存在。
"临时工"就像线程池中的非核心线程。只有在双11,双12这种高峰期,当快递数量超过正式员工能处理的数量时,临时工就会被雇佣起来。而当高峰期过后,如果一段时间内没有新的快递到来,临时工就会被解雇。这就像线程池中的非核心线程,当任务量大于核心线程数量时,非核心线程会被创建,而在一段时间内没有新任务到来时,非核心线程会被终止。
"仓库"就是线程池中的任务队列。当所有的快递员(线程)都在投递快递(执行任务)时,新来的快递(任务)会被暂时存放在仓库中等待投递。但是仓库的容量是有限的,如果仓库满了,公司就需要采取一些策略,比如拒绝接收新的快递(拒绝新的任务),或者雇佣更多的临时工(创建更多的非核心线程)。
快递员从仓库拿快递去送,就像线程从任务队列中取出任务去执行。他们总是按照一定的顺序(比如先来先服务)来取快递,执行任务。
2. 原生线程池(ThreadPoolExecutor)
ThreadPoolExecutor提供了更多的参数,可以进一步细化线程池的行为
🍬ThreadPoolExecutor的构造方法:
🍬构造方法参数解析:对应上述例子来结合理解
🍃corePoolSize,核心线程数:正式员工
🍃maximumPoolSize,最大线程数:正式员工和临时员工总数
🍃keepAliveTime,空闲时间:临时工空闲(keepAliveTime,TimeUnit结合来决定何时解雇临时工)
🍃TimeUnit,空闲时间单位:空闲时间(keepAliveTime,TimeUnit结合来决定何时解雇临时工)
🍃workQueue,阻塞队列:快递仓库
🍃threadFactory:使用工厂对象提供的方法来创建线程(了解)
🍃RejectedExecutionHandler,拒绝策略:仓库满了不再接收快递
🍬拒绝策略:
🍂AbortPolicy():以抛出异常的方式拒绝(默认的拒绝策略)
🍂CallerRunsPolicy():让调用的线程来处理
🍂DiscardOldestPolicy():丢弃时间最久的任务(先进先出)
🍂DiscardPolicy():丢弃新来的任务
👁实现代码:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
//使用原生api来创建(ThreadPoolExecutor)
public class ThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, //线程核心数
10, //最大线程数
60, //空闲时间
TimeUnit.SECONDS, //空闲时间单位
new ArrayBlockingQueue<>(100), //阻塞队列
new ThreadPoolExecutor.AbortPolicy() //默认拒绝策略,抛出异常
// new ThreadPoolExecutor.CallerRunsPolicy() 让调用线程自己处理
// new ThreadPoolExecutor.DiscardOldestPolicy() 丢弃时间最久任务
// new ThreadPoolExecutor.DiscardPolicy() 丢弃新来的任务
);
//提交任务使用:submit/execute
for(int i = 0;i < 10;i++){
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}
3. 标准库中的线程池 (Executors)
🍃1.使用 Executors.newFixedThreadPool(10) 能创建出固定包含 10 个线程的线程池.
🍃2.返回值类型为 ExecutorService
🍃3.通过 ExecutorService.submit 可以注册一个任务到线程池中 .
3.1 提交方法
ExecutorService 类 API:
方法 | 说明 |
---|---|
void execute(Runnable command) | 执行任务(Executor 类 API) |
Future<?> submit(Runnable task) | 提交任务 task() |
Future submit(Callable task) | 提交任务 task,用返回值 Future 获得任务执行结果 |
List<Future> invokeAll(Collection<? extends Callable> tasks) | 提交 tasks 中所有任务 |
List<Future> invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit) | 提交 tasks 中所有任务,超时时间针对所有task,超时会取消没有执行完的任务,并抛出超时异常 |
T invokeAny(Collection<? extends Callable> tasks) | 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其它任务取消 |
execute 和 submit 都属于线程池的方法,对比:
-
execute 只能执行 Runnable 类型的任务,没有返回值; submit 既能提交 Runnable 类型任务也能提交 Callable 类型任务,底层是封装成 FutureTask,然后调用 execute 执行
execute()
:如果任务中有未捕获的异常,它会在执行该任务的线程中抛出,可能导致线程终止,但不会影响调用execute()
的线程。submit()
:如果任务中有异常,它会被封装在Future
对象中,可以通过调用Future.get()
方法在调用submit()
的线程中捕获并处理。
3.2 关闭方法
ExecutorService 类 API:
方法 | 说明 |
---|---|
void shutdown() | 线程池状态变为 SHUTDOWN,等待任务执行完后关闭线程池,不会接收新任务,但已提交任务会执行完,而且也可以添加线程(不绑定任务) |
List shutdownNow() | 线程池状态变为 STOP,用 interrupt 中断正在执行的任务,直接关闭线程池,不会接收新任务,会将队列中的任务返回 |
boolean isShutdown() | 不在 RUNNING 状态的线程池,此执行者已被关闭,方法返回 true |
boolean isTerminated() | 线程池状态是否是 TERMINATED,如果所有任务在关闭后完成,返回 true |
boolean awaitTermination(long timeout, TimeUnit unit) | 调用 shutdown 后,由于调用线程不会等待所有任务运行结束,如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待 |
使用Executors 工厂类创建一个线程池:
注意这行代码:
ExecutorService threadPool = Executors.newFixedThreadPool(10);
newFixedThreadPool 是 Executors 类的一个静态方法,像这种借助静态方法创建实例,这样的方法,称为"工厂方法",对应的设计模式,就叫做"工厂模式"。
通常情况下,创建对象,是借助 new ,调用构造方法来实现的。但是 Java 里面的构造方法,有诸多限制,在很多时候不方便使用,因此就需要给构造方法再包装一层,外层起到包装作用的方法就是工厂方法!!
🍃为什么构造方法有时候不方便使用??
构造方法的限制在于,当前构造方法的名字务必是和类名一样。要想实现不同版本的构造,就得通过重载,但是重载又要求参数类型和个数不同。当我们的场景刚好是参数类型相同且参数个数相同的时候,重载就行不通了!!请看以下例子:
当我需要用构造方表示两个点:一个是笛卡尔坐标系,构造点;一个是极坐标系构造点。
class Point {
// 笛卡尔坐标系,构造点
public Point(double x, double y) { };
// 使用极坐标系,构造点
public Point(double r, double a) { };
// Error : 编译错误
}
显然,这段代码编译错误,不符合重载规则,这时候我们就需要使用工厂模式来解决上述问题了
class Point {
// 笛卡尔坐标系,构造点
public static Point makePointByXY(double x, double y) {
Point point = new Point();
point.setX(x);
point.setY(y);
return point;
}
// 极坐标系,构造点
public static Point makePointByRA(double r, double a) {
Point point = new Point();
point.setR(r);
point.setA(a);
return point;
}
}
于是此时实例化只需要通过类名调用需要的静态方法即可创建!!
🍁Executors 创建线程池的几种方式:
🍃1.newFixedThreadPool: 创建固定线程数的线程池
🍃2.newCachedThreadPool: 创建线程数目动态增长的线程池 .
🍃3.newSingleThreadExecutor: 创建只包含单个线程的线程池 .
🍃4.newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令 . 是进阶版的 Timer.
Executors 里面的各种工厂方法,其实都是针对 ThreadPoolExecutor 这个类进行了 new 并且闯入不同风格的参数,来达到构造不同种类线程池的目标!!
4. 自定义一个线程池
//自定义一个线程池
class MyThreadPool {
//阻塞队列用来存放任务
private BlockingQueue<Runnable> queue =new LinkedBlockingDeque<>();
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
//此处实现一个固定线程数的线程池
public MyThreadPool(int 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 ThreadDemo1 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool myThreadPool =new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int number =i;
myThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello "+number);
}
});
}
}
}