0
点赞
收藏
分享

微信扫一扫

CrossOver软件2024免费 最新版本详细介绍 CrossOver软件好用吗 Mac电脑玩Windows游戏

冬冬_79d4 2024-04-02 阅读 9
java-eejava

目录

🚩什么是线程池

🎈从池子中取效率大于新创建线程效率(why)

🚩标准库中的线程池

🎈为什么调用方法而不是直接创建对象 

🎈工厂类里的方法

📝newCachedThreadPool()

📝newFixedThreadPool() 

🚩实现线程池

🚩ThreadPoolExecutor

🎈ThreadPoolExecutor的构造器参数

🎈ThreadPoolExecutor的新任务拒绝策略


🚩什么是线程池

首先我们想象一个场景就是比较渣的场景,我是一个男生,很帅并且有才华,追问的人呢,很多,排成了一个长队。然后我就挑了一个既好看又有钱又有才华的女生进行交往,交往一段时候后,我腻歪了,想换个女朋友,此时我要做俩个事情:1>想办法分手,2>再找一个小哥哥,培养感情。此时进行这俩个操作的时候,效率是很低的,有没有办法优化呢?

优化:我和这个女生A再交往的过程中,同时再和另一个女生B搞暧昧(培养感情),当我想和女生A分手的时候,就只要分手了后我们就可以和女生B直接在一起了。(此时我和女生B感情是有一定的基础了)。此时女生B就是我们所说的备胎。

进一步优化:我需要更高的效率的话,更换女朋友,就可以再和女生A在一起的时候,同时和女生B,C,D交往联络感情,此时女生B,C,D都是我的备胎,此时备胎就构成了——备胎池。

所以和线程池有同样的方式,线程池顾名思义就是存放线程的池子,等需要了就直接调用了,省去了启动和销毁的损耗了。


从上面线程池我们知道,等需要了就直接从线程池中取,但是为什么从池子取得效率比新创建线程得效率更高呢?

🎈从池子中取效率大于新创建线程效率(why)

如果一段程序,是在系统内核中执行,此时就称为"内核态",如果不是,则称为"用户态".

操作系统,是由 内核+配套 的应用程序构成的,内核则是 系统最核心的部分,创建线程操作,就需要调用系统api,进入内核中,按照内核态的方式来完成一系列操作。


🚩标准库中的线程池

🎈为什么调用方法而不是直接创建对象 

创建对象的同时,new关键字就会触发类的构造方法,但是构造方法,存在一定的局限性。

考虑有个类,我们期待用笛卡尔坐标系,来构造对象,又或者用极坐标构造对象,写在一起的时候,这俩个方法是无法重载的(也就是说在一个类中我们要实现不同方式的初始化),就会编译失败。其实很多时候,构造一个对象,希望有多种构造方式,多种方式,就需要使用多个版本的构造方法来分别实现,但是构造方法要求方法的名字必须是类名,不同的构造方法,就只能通过 重载 的方式区分了。(重载是方法名相同,参数个数类型不同),使用工厂模式/设计模式,就能解决这个问题,使用普通的方法,代替构造方法来完成初始化工作,普通方法就可以通过方法名的不同来进行区分了,不必因为重载的规则而限制了。

通过这种,我们通过一个工厂类Executors调用方法创建不同类型的初始化工作。Executors是工厂类,那么调用的方法是工厂方法,然后加工好之后,返回的是整个加工好的线程,而ExecutorService就是线程池,是由工厂类调用工厂方法创建好的。


🎈工厂类里的方法

Executors 创建线程池的几种方式
newFixedThreadPool: 创建固定线程数的线程池
newCachedThreadPool: 创建线程数目动态增长的线程池.
newSingleThreadExecutor: 创建只包含单个线程的线程池.
newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
Executors 本质上是 ThreadPoolExecutor 类的封装.


🚩实现线程池

package ThreadPool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class MyThread{
BlockingQueue<Runnable> queue=new ArrayBlockingQueue<Runnable>(1000);
public void submit(Runnable runnable){
queue.offer(runnable);
}
public void takeTask(int n){
for (int i = 0; i < n; i++) {
Thread t=new Thread(()->{
try {
Runnable runnable=queue.take();
runnable.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
}
}
}
public class ThreadPool_test {
public static void main(String[] args) {
MyThread myThread=new MyThread();
for (int i = 0; i <100; i++) {//一百个任务
myThread.submit(new Runnable() {
@Override
public void run() {
System.out.println("我爱zyf");
}
});
}
myThread.takeTask(10);//10个线程执行100个任务
}
}

 打印了十个,10个线程执行了10个任务,因为里面没有用while(true)循环,一个线程执行完任务之后就结束了。但是这些线程是可能同时执行各自的任务,但是一个线程肯定是执行一个任务。


🚩ThreadPoolExecutor

在阿里巴巴手册中有一条建议:

【强制】线程池不允许使用 Executors 去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

如果经常基于Executors提供的工厂方法创建线程池,很容易忽略线程池内部的实现。特别是拒绝策略,因使用Executors创建线程池时不会传入这个参数,直接采用默认值,所以常常被忽略

ThreadPoolExecutor可以实现线程池的创建。ThreadPoolExecutor相关类图如下

从类图可以看出,ThreadPoolExecutor最终实现了Executor接口,是线程池创建的真正实现者。


ThreadPoolExecutor核心方法有俩个,一个是构造方法,一个是注册任务(添加方法).

🎈ThreadPoolExecutor的构造器参数

 public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
  • 参数一
  • 指定线程池的线程数量(核心线程): corePoolSize,不能小于0;
  • 参数二
  • 指定线程池可支持的最大线程数: maximumPoolSize,最大数量 >= 核心线程数量;
  • 参数三
  • 指定临时线程的最大存活时间: keepAliveTime,不能小于0;(则表示实习生可以摸鱼的时间,并不代表一摸鱼就被开除了)
  • 参数四
  • 指定存活时间的单位(秒、分、时、天): unit,时间单位;
  • 参数五
  • 指定任务队列: workQueue,不能为null;
  • 参数六
  • 指定用哪个线程工厂创建线程: threadFactory,不能为null;
  • 参数七
  • 指定线程忙,任务满的时候,新任务来了怎么办: handler,不能为null;
  • 临时线程触发机制
  • 新任务提交时发现核心线程都被占用,任务队列也满了,但还可以创建临时线程,此时才会创建临时线程。
  • 何时拒绝任务
  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。

🎈ThreadPoolExecutor的新任务拒绝策略

在面试中,拒绝策略和线程数目是面试的重点。


举报

相关推荐

0 条评论