当前位置: 首页 > news >正文

面试官:不会真有人不知道什么是线程池吧?

目录

1.什么是线程池

2.为什么要使用线程池

2.1 降低资源消耗

2.2 提高系统的响应速度

2.3 防止过多的线程搞坏系统

3.线程池主要参数

3.1 corePoolSize

3.2 maximumPoolSize

3.3 keepAliveTime

3.4 unit

3.5 workQueue

3.6 threadFactory

3.7 handler

4.线程池执行流程

5.Executors提供的线程池

5.1 newSingleThreadExecutor

5.2 newFixedThreadPool

5.3 newCachedThreadPool

5.4 newScheduledThreadPool

6.为什么阿里巴巴禁止程序员用Exectors创建线程池

7.总结


1.什么是线程池

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位,我们的程序最终都是由线程进行运作。在Java中,创建和销毁线程的动作是很消耗资源的,因此就出现了所谓“池化资源”技术。线程池是池化资源技术的一个应用,所谓线程池,顾名思义就是预先按某个规定创建若干个可执行线程放入一个容器中(线程池),需要使用的时候从线程池中去取,用完之后不销毁而是放回去,从而减少了线程创建和销毁的次数,达到节约资源的目的。

2.为什么要使用线程池

2.1 降低资源消耗

前面已经讲到线程池的出现减少了线程创建和销毁的次数,每个线程都可以被重复利用,可执行多个任务。

2.2 提高系统的响应速度

每当有任务到来时,直接复用线程池中的线程,而不需要等待新线程的创建,这个动作可以带来响应速度的提升

2.3 防止过多的线程搞坏系统

可以根据系统的承受能力,调整线程池中的工作线程的数量,防止因为线程过多服务器变慢或死机。java一个线程默认占用空间为1M,可以想象一旦手动创建线程过多极有可能导致内存溢出。

3.线程池主要参数

我们可以用Executors类来创建一些常用的线程池,但是像阿里是禁止直接通过Executors类直接创建线程池的,具体的原因稍后再谈。

在了解Executors类所提供的几个线程池前,我们首先来了解一下ThreadPoolExecutor的主要参数,ThreadPoolExecutor是创建线程池的类,我们选取参数最多的构造方法来看一下:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

名称

类型

含义

corePoolSize

int

核心线程池的大小

maximumPoolSize

int

最大线程池大小

keepAliveTime

long

线程最大空闲时间

unit

TimeUnit

时间单位

workQueue

BlockingQueue<Runnable>

线程等待队列

threadFactory

ThreadFactory

线程创建工程

handler

RejectedExecutionHandler

拒绝策略

3.1 corePoolSize

当向线程池提交一个任务时,如果线程池中已创建的线程数量小于corePoolSIze,即便存在空闲线程,也会创建一个新线程来执行任务,直到创建的线程数大于或等于corePoolSIze。

3.2 maximumPoolSize

线程池所允许的最大线程个数,当队列满了且已经创建的线程数小于maximumPoolSize时,会创建新的线程执行任务。

3.3 keepAliveTime

当线程中的线程数大于corePoolSIze时,如果线程空闲时间大于keepAliveTime,该线程就会被销毁。

3.4 unit

keepAliveTime时间单位

3.5 workQueue

用于保存等待执行任务的队列

3.6 threadFactory

用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n

3.7 handler

拒绝策略,当线程池和队列满了之后,再加入新线程后会执行此策略。

下面是四种线程池的拒绝策略:

AbortPolicy:中断任务并抛出异常

DiscardPolicy:中段任务但是不抛出异常

DiscardOldestPolicy:丢弃队列中最老的任务,然后尝试提交新任务

CallerRunsPolicy:由调用线程处理该任务

4.线程池执行流程

当我们了解了ThreadPoolExecutor的七个参数后,我们就可以很快的理解线程池的流程:

当提交任务后,首先判断当前线程数是否超过核心线程数,如果没超过则创建新线程执行任务,否则判断工作队列是否已满,如果未满则将任务添加到队列中,否则判断线程数是否超过最大线程数,如果未超过则创建线程执行任务,否则执行拒绝策略。

5.Executors提供的线程池

executors提供了许多种线程池供用户使用,虽然很多公司禁止使用executors创建线程池,但是对于刚开始解除线程池的人来说,Executors类所提供的线程池能很好的带你进入多线程的世界。

5.1 newSingleThreadExecutor

ExecutorService executorService = Executors.newSingleThreadExecutor();

听名字就可以知道这是一个单线程的线程池,在这个线程池中只有一个线程在工作,相当于单线程执行所有任务,此线程可以保证所有任务的执行顺序按照提交顺序执行,看构造方法也可以看出,corePoolSize和maximumPoolSize都是1。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

5.2 newFixedThreadPool

ExecutorService executorService = Executors.newFixedThreadPool(2);

固定长度的线程池,线程池的长度在创建时通过变量传入。下面是newFixedThreadPool的构造方法,corePoolSize和maximumPoolSize都是传入的参数值

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

5.3 newCachedThreadPool

ExecutorService executorService = Executors.newCachedThreadPool();

可缓存线程池,这个线程池设定keepAliveTime为60秒,并且对最大线程数量几乎不做控制。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

观察构造方法,corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制。设定keepAliveTime 为60秒,线程空闲60秒后自动结束,因为该线程池创建无限制,不会有队列等待,所以使用SynchronousQueue同步队列。

5.4 newScheduledThreadPool

创建一个定时的线程池。此线程池支持定时以及周期性执行任务的需求。下面是newScheduledThreadPool的用法:

Thread thread1=new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread1");
    }
});
Thread thread2=new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread2");
    }
});
Thread thread3=new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"thread3");
    }
});
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
//在1000ms后执行thread1
scheduledExecutorService.schedule(thread1,1000,TimeUnit.MILLISECONDS);
//在1000ms后每隔1000ms执行一次thread2,如果任务执行时间比间隔时间长,则延迟执行
scheduledExecutorService.scheduleAtFixedRate(thread2,1000,1000,TimeUnit.MILLISECONDS);
//和第二种方式类似,但下一次任务开始的时间为:上一次任务结束时间(而不是开始时间) + delay时间
scheduledExecutorService.scheduleWithFixedDelay(thread3,1000,1000,TimeUnit.MILLISECONDS);

6.为什么阿里巴巴禁止程序员用Exectors创建线程池

如果你的idea装了Alibaba Java Codeing Guidelines插件(推荐大家使用,有助于让你的代码更加规范),那么当你写了Exectors创建线程池后会看到提示:

并且阿里将这个用法定义为Blocker,即不允许使用,而是让人们用ThreadPoolExecutor的方式创建线程池。原因是通过ThreadPoolExecutor的方式,这样的处理方式让写的人更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池对象的弊端如下:

1)FixedThreadPool和SingleThreadPool:

  允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

2)CachedThreadPool:

  允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

下面是ThreadPoolExecutor创建线程池的简单例子

int corePoolSize=5;
int maximumPoolSize=10;
long keepAliveTime=30;
BlockingQueue<Runnable> blockingQueue=new ArrayBlockingQueue(2);
RejectedExecutionHandler handler=new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, blockingQueue, handler);
threadPoolExecutor.execute(thread1);

7.总结

关于本篇文章如果有任何错误、疑问或者补充欢迎大家留言或私信我。欢迎关注我的微信公众号《Java鱼仔》与我交流,每天进步一点点。

相关文章:

  • 从零搭建基于SpringBoot的秒杀系统(一):项目准备
  • 【总结】oracle恢复误删除数据,解除锁定的等sql语句
  • 从零搭建基于SpringBoot的秒杀系统(二):快速搭建一个SpringBoot项目
  • 重拾cgi——cgi dispatcher
  • 从零搭建基于SpringBoot的秒杀系统(三):首页、详情页编写
  • 从零搭建基于SpringBoot的秒杀系统(四):雪花算法生成订单号以及抢购功能实现
  • 操作系统实验一 命令解释程序的编写
  • 从零搭建基于SpringBoot的秒杀系统(五):基于Shiro的人员登陆认证
  • 从零搭建基于SpringBoot的秒杀系统(六):使用RabbitMQ让订单指定时间后失效
  • 从零搭建基于SpringBoot的秒杀系统(七):高并发导致超卖问题分析处理
  • 从零搭建基于SpringBoot的秒杀系统(八):通过分布式锁解决多线程导致的问题
  • 读《世界是数字的》有感
  • 面试官问我:什么是静态代理?什么是动态代理?注解、反射你会吗?
  • redis入门到精通系列(十):springboot集成redis及redis工具类的编写
  • css3延时动画
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • [ JavaScript ] 数据结构与算法 —— 链表
  • [译]CSS 居中(Center)方法大合集
  • 【EOS】Cleos基础
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • CentOS 7 修改主机名
  • CNN 在图像分割中的简史:从 R-CNN 到 Mask R-CNN
  • CSS 提示工具(Tooltip)
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • emacs初体验
  • JAVA SE 6 GC调优笔记
  • JavaScript设计模式之工厂模式
  • ReactNative开发常用的三方模块
  • Webpack 4x 之路 ( 四 )
  • 大型网站性能监测、分析与优化常见问题QA
  • 简单基于spring的redis配置(单机和集群模式)
  • 离散点最小(凸)包围边界查找
  • 模型微调
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • ​LeetCode解法汇总518. 零钱兑换 II
  • ​卜东波研究员:高观点下的少儿计算思维
  • ​第20课 在Android Native开发中加入新的C++类
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • $.each()与$(selector).each()
  • (5)STL算法之复制
  • (C11) 泛型表达式
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (附源码)springboot 校园学生兼职系统 毕业设计 742122
  • (附源码)springboot高校宿舍交电费系统 毕业设计031552
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (十三)Flask之特殊装饰器详解
  • (一)C语言之入门:使用Visual Studio Community 2022运行hello world
  • (一)基于IDEA的JAVA基础1
  • (一一四)第九章编程练习
  • (转)linux 命令大全
  • (转)winform之ListView
  • ./和../以及/和~之间的区别
  • .halo勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .htaccess配置重写url引擎