线程池ThreadPoolExecutor
2022-12-11 23:45 杭伟 阅读(119) 评论(0) 编辑 收藏 举报回顾Java创建线程的几种方式:
1,继承Thread(实际Thread也是实现Runnable接口);
2,实现Runnable接口;
3,实现Callable接口(返回值);
4,由线程池创建。
根据阿里巴巴Java开发手册,关于并发编程有两个强制要求:
1.线程资源必须通过线程池提供,不允许在应用中自行显示创建线程。
2.线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,规避资源耗尽风险。
解释:
第1点好理解,线程的创建,挂起,唤醒都会引起线程内核态和用户态的切换,频繁的创建会导致“过度切换”问题。
第2点,先来看看Executors创建线程池的方式:
newFixedThreadPool
newSingleThreadExecutor
newFixedThreadPool和newSingleThreadExecutor的核心线程数和最大线程数都是(传参)常量,但是它们的等待队列都用了LinkedBlockingQueue,
这是一个线程安全的单向链表队列,但是队列长度就趋于无界(也有人叫无界队列)。实际应用中很可能会造成任务积压,进而导致OOM。
newCachedThreadPool
newScheduledThreadPool
newCachedThreadPool和newScheduledThreadPool就更加直观,允许的最大线程数为Int的最大值,生产中可能会因创建大量线程导致OOM。
故阿里巴巴强制不允许使用Executors创建线程池。
另外,这四种通过Executors创建线程池的方式,底层都是通过new ThreadPoolExecutor(...)的方式,故而,
推荐,直接使用new ThreadPoolExecutor()的方式创建线程池,或者继承ThreadPoolExecutor类来自定义线程池。
比如github有名的hippo4j项目,ExtensibleThreadPoolExecutor类
就是通过继承ThreadPoolExecutor的方式,自定义线程池。
ThreadPoolExecutor的使用
创建线程池:
/** * 1 核心线程数 * 2 最大线程数 * 3 非核心线程空闲时的存活时间 * 4 时间单位 * 5 阻塞队列 * 6 线程工厂 * 7 拒绝策略 **/ ExecutorService es = new ThreadPoolExecutor(10, 50, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(); t.setName("ceshi1"); return t; } }, new ThreadPoolExecutor.AbortPolicy());
线程池的5种状态:
状态说明:
RUNNING:能够接收新任务以及完成现有任务
SHUTDOWN:不接收新任务,但会继续处理现有任务(包括正在进行的任务和阻塞队列中的任务)
STOP:严重,不接收新任务,现有任务也会中断(也包括丢弃阻塞队列中的任务)
TIDYING:线程池即将被销毁的中间状态
TERMINATED:线程池彻底终止。
ThreadPoolExecutor的原理
线程复用原理
通过while循环不停的从阻塞队列中获取任务来执行,线程和任务已解耦,
何时将设定的核心线程数建满?
假设我们的主代码在并发环境下,会有超过2个以上的并发请求在调用execute,
对于每一个请求,都会通过addWorker方法创建一个线程,直到总的线程数达到核心线程数为止。
corePoolSize用volatile修饰,对多线程是可见的。
比如创建了10个核心线程的线程池,需要通过并发工具来模拟这个情况,否则单机模拟不出这个情况。
作者:hangwei
出处:http://www.cnblogs.com/hangwei/
关于作者:专注于开源平台,分布式系统的架构设计与开发、数据库性能调优等工作。如有问题或建议,请多多赐教!
版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
如果您觉得文章对您有帮助,可以点击文章右下角“推荐”一下。您的鼓励是作者坚持原创和持续写作的最大动力!