干货,阿里P8浅谈对java线程池的理解(面试必备)
线程池的概念
线程池由任务队列和工作线程组成,它可以重用线程来避免线程创建的开销,在任务过多时通过排队避免创建过多线程来减少系统资源消耗和竞争,确保任务有序完成;ThreadPoolExecutor 继承自 AbstractExecutorService 实现了 ExecutorService 接口,ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor 实现了 ExecutorService 和 ScheduledExecutorService 接口
//有多个构造方法,最终都指向这个最多参数的构造方法 public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
corePoolSize:核心运行的线程个数,也就是当超过这个范围的时候就需要将新的异步任务放入到等待队列中,小于这个数时添加进来的异步任务一般直接新建Thread 执行;
maximumPoolSize:最大线程个数,当大于了这个值就会将准备新加的异步任务由一个丢弃处理机制来处理,大于 corePoolSize 且小于 maximumPoolSize 则新建 Thread 执行,但是当通过newFixedThreadPool 创建的时候,corePoolSize 和 maximumPoolSize 是一样的,而corePoolSize 是先执行的,所以他会先被放入等待队列而不会执行到下面的丢弃处理中;
workQueue:任务等待队列,当达到 corePoolSize的时候就向该等待队列放入线程信息(默认为一个LinkedBlockingQueue);
keepAliveTime:默认是 0,当线程没有任务处理后空闲线程保持多长时间,不推荐使用;
threadFactory:是构造 Thread 的方法,一个接口类,可以使用默认的 default实现,也可以自己去包装和传递,主要实现 newThread 方法即可;
defaultHandler:当参数 maximumPoolSize 达到后丢弃处理的方法实现,java 提供了 5种丢弃处理的方法,当然也可以自己弄,主要是要实现接口 RejectedExecutionHandler 中rejectedExecution(Runnabler, ThreadPoolExecutor e) 方法,java 默认使用的是AbortPolicy,他的作用是当出现这种情况的时候抛出一个异常;通常得到线程池后会调用其中的 submit 或 execute 方法去提交执行异步任务,其实 submit 方法最终会调用execute 方法来进行操作,只是他提供了一个 Future
来托管返回值的处理而已,当你调用需要有返回值的信息时用它来处理是比较好的,这个 Future 会包装 Callable 信息。
BlockingQueue有四个具体的实现类,根据不同需求,选择不同的实现类
- ArrayBlockingQueue:一个由数组支持的有界阻塞队列,规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的。
- LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的。
- PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序。
- SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。
LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。
说了这么多的概念估计啥也不清楚,带大家写个例子,一边看代码一边看概念会理解的很快
public class ThreadPoolTest { private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = Math.max(4, Math.min(CPU_COUNT - 1, 5)); private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 2; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(10); //一共执行20个任务 ,核心线程数是4,最大核心线程数是10,目前加入的runnable20个(相当于20个任务), //20个任务需要执行,但是核心线程数只有4个,还有16个任务,由于LinkedBlockingQueue队列是最大存放的任务为10 个,队列满了,则会创建新的线程去执行任务,这个时候最大线程是10, 非核心线LinkedBlockingQueue数还有6个,这时候会开6个线程去执行, 目前达到10个最大线程数,此时队列里面还有10个。正好满足队列的大小 static { System.out.println("核心线程数=" + CORE_POOL_SIZE); System.out.println("最大线程数=" + MAXIMUM_POOL_SIZE); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, //核心线程数 MAXIMUM_POOL_SIZE, //线程池中最大的线程数 60, //线程的存活时间,没事干的时候,空闲的时间 TimeUnit.SECONDS, //线程存活时间的单位 sPoolWorkQueue, //线程缓存队列 new ThreadFactory() { //线程创建工厂,如果线程池需要创建线程会调用newThread来创建 @Override public Thread newThread(@NonNull Runnable r) { Thread thread = new Thread(r); thread.setDaemon(false); return thread; } }); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; } public static void main(String[] args) { for (int i = 0; i < 20; i++) { Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行完毕" + Thread.currentThread().getName()); } }; //丢给线程池去执行 THREAD_POOL_EXECUTOR.execute(runnable); } } }
核心的解释,大家请看注释
运行效果
看这个例子
public class ThreadPoolTest { private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = Math.max(4, Math.min(CPU_COUNT - 1, 5)); private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 2; private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(9); //一共执行20个任务 ,核心线程数是4,最大核心线程数是10,目前加入的runnable20个(相当于20个任务), //20个任务需要执行,但是核心线程数只有4个,还有16个任务,由于LinkedBlockingQueue队列是最大存放的任务为9个,队列满了,则会创建新的线程去执行任务 //这个时候最大线程是10,非核心线程数还有6个,这时候会开6个线程去执行,目前达到10个最大线程数,此时队列里面最大只能存放9个, //还有一个Runnable,此时就会报错RejectedExecutionException static { System.out.println("核心线程数=" + CORE_POOL_SIZE); System.out.println("最大线程数=" + MAXIMUM_POOL_SIZE); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( CORE_POOL_SIZE, //核心线程数 MAXIMUM_POOL_SIZE, //线程池中最大的线程数 60, //线程的存活时间,没事干的时候,空闲的时间 TimeUnit.SECONDS, //线程存活时间的单位 sPoolWorkQueue, //线程缓存队列 new ThreadFactory() { //线程创建工厂,如果线程池需要创建线程会调用newThread来创建 @Override public Thread newThread(@NonNull Runnable r) { Thread thread = new Thread(r); thread.setDaemon(false); return thread; } }); threadPoolExecutor.allowCoreThreadTimeOut(true); THREAD_POOL_EXECUTOR = threadPoolExecutor; } public static void main(String[] args) { for (int i = 0; i < 20; i++) { Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行完毕" + Thread.currentThread().getName()); } }; //丢给线程池去执行 THREAD_POOL_EXECUTOR.execute(runnable); } } }
这个例子就是把LinkedBlockingQueue的大小改为了9个,具体的解释,请大家看注释;大家可以清楚的知道RejectedExecutionException 报错的原因,其实是AsyncTask一些隐患,比如去执行200个Runnable 肯定会报错
运行效果
相信大家看例子的同时在结合概念会很清楚的理解了java线程池
写在最后,欢迎留言讨论,持续更新!!!
干货,阿里P8浅谈对java线程池的理解(面试必备)的更多相关文章
- 干货:教你如何监控 Java 线程池运行状态
之前写过一篇 Java 线程池的使用介绍文章<线程池全面解析>,全面介绍了什么是线程池.线程池核心类.线程池工作流程.线程池分类.拒绝策略.及如何提交与关闭线程池等. 但在实际开发过程中, ...
- java线程池ThreadPoolExecutor理解
Java通过Executors提供四种线程池,分别为:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.newFixe ...
- java线程池初步理解
多线程基础准备 进程:程序的执行过程,持有资源和线程 线程:是系统中最小的执行单元,同一个进程可以有多个线程,线程共享进程资源 线程交互(同步synchronized):包括互斥和协作,互斥通过对象锁 ...
- 浅谈对java中锁的理解
在并发编程中,经常遇到多个线程访问同一个 共享资源 ,这时候作为开发者必须考虑如何维护数据一致性,在java中synchronized关键字被常用于维护数据一致性.synchronized机制是给共享 ...
- Java线程池详解(二)
一.前言 在总结了线程池的一些原理及实现细节之后,产出了一篇文章:Java线程池详解(一),后面的(一)是在本文出现之后加上的,而本文就成了(二).因为在写完第一篇关于java线程池的文章之后,越发觉 ...
- Java线程池的底层实现与使用
前言 在我们进行开发的时候,为了充分利用系统资源,我们通常会进行多线程开发,实现起来非常简单,需要使用线程的时候就去创建一个线程(继承Thread类.实现Runnable接口.使用Callable和F ...
- 20190608_浅谈go&java差异(三)
20190608_浅谈go&java差异(三) 转载请注明出处https://www.cnblogs.com/funnyzpc/p/10990703.html 第三节内容概览 多线程通讯(线程 ...
- 干货 | 教你如何监控 Java 线程池运行状态
之前写过一篇 Java 线程池的使用介绍文章<线程池全面解析>,全面介绍了什么是线程池.线程池核心类.线程池工作流程.线程池分类.拒绝策略.及如何提交与关闭线程池等. 但在实际开发过程中, ...
- Java线程池进阶
线程池是日常开发中常用的技术,使用也非常简单,不过想使用好线程池也不是件容易的事,开发者需要不断探索底层的实现原理,才能在不同的场景中选择合适的策略,最大程度发挥线程池的作用以及避免踩坑. 一.线程池 ...
随机推荐
- 123457123456#0#-----com.threeapp.XianshiDaDiShu03-----现实版打地鼠03
com.threeapp.XianshiDaDiShu03-----现实版打地鼠03
- 123457123457#0#-----com.threeapp.PaoPaoLong01-----泡泡龙大作战01
com.threeapp.PaoPaoLong01-----泡泡龙大作战01
- tcpdump抓包代码
tcpdump - tcp[:]=:]=0x4854 or tcp 抓出来的包可以导入wireshark分析 以上代码曾经在ios越狱机器上使用,用于抓包,具体也记不起来了 导入wireshark效果
- delphi 根据特殊符号字符获取字符串前或后的字符
function GetBefore(substr, str:string):string; {©Drkb v.3(2007): www.drkb.ru, ®Vit (Vitaly Nevzorov) ...
- (十九)oracle 基础使用以及sql语句基础
oracle的安装与卸载 要记住数据库口令,适用于sys.system.sysman/dbsnmp等账户,而scott帐号密码默认为tiger, 以oracle 10g来说,scott账户默认是lo ...
- 禁止CSRF校验实例
静态文件----提交表单 本例在static目录中建立了一个from.html静态页面,该页面有一个<form>标签,用于向服务端提交POST请求,然后在post.py脚本文件中添加一个路 ...
- iOS-二维码扫描界面(转)
网址学习:http://blog.csdn.net/linux_zkf/article/details/7724867 二维码扫描界面自定义 作者:朱克锋 邮箱:zhukefeng@iboxp ...
- Laravel增加CORS中间件完成跨域请求
原文地址: 跨域的请求 出于安全性的原因,浏览器会限制 Script 中的跨域请求.由于 XMLHttpRequest 遵循同源策略,所有使用 XMLHttpRequest 构造 HTTP 请求的应用 ...
- Flask框架(2)--编写简单的用户注册--登录场景
为了更好的理解web前后端的工作业务逻辑:本笔记记录用flask框架编写的一个最初级的代码实现简单的用户注册,登录场景: 初次进入首页,提示--游客,欢迎参观,有登录和注册选项, 登录成功后的用户,会 ...
- php5.6安装及php-fpm优化配置
1,安装依赖包: yum install -y gcc gcc-c++ zlib zlib-devel pcre pcre-devel gd libjpeg libjpeg-devel libpn ...