线程池深入(li)
java线程池。在jdk5之后为我们提供了线程池,只需要使用API,不用去考虑线程池里特殊的处理机制。jdk5线程池分好多种,固定尺寸的线程池、可变尺寸连接池等。常用的是ThreadPoolExecutor,它的构造方法如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
参数说明:
1.corePoolSize:线程池维护线程的最少数量,有可能是空闲的线程。
2.maximunPoolSize:线程池维护线程的最大数量。
3.keepAliveTime:线程池维护线程所允许的空闲时间。
4.TimeUnit:程池维护线程所允许的空闲时间的单位。
5.workQueue:线程池所使用的缓冲队列,改缓冲队列的长度决定了能够缓冲的最大数量。
6.RejectedExecutionHandler :拒绝任务的处理方式。
拒绝任务,是指当线程池里面的线程数量达到 maximumPoolSize 且 workQueue 队列已满的情况下被尝试添加进来的任务。在 ThreadPoolExecutor 里面定义了 种 handler 策略,分别是:
1.CallerRunsPolicy :这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功。
2.AbortPolicy :对拒绝任务抛弃处理,并且抛出异常。
3.DiscardPolicy :对拒绝任务直接无声抛弃,没有异常信息。
4.DiscardOldestPolicy :对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。
一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个Runnable类型的对象,任务的执行方法就是 Runnable 类型对象的run()方法。当一个任务通过 execute(Runnable) 方法欲添加到线程池时,线程池采用的策略如下:
1.如果此时线程池中的数量小于 corePoolSize ,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
2.如果此时线程池中的数量等于 corePoolSize ,但是缓冲队列 workQueue 未满,那么任务被放入缓冲队列。
3.如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量小于maximumPoolSize ,建新的线程来处理被添加的任务。
4.如果此时线程池中的数量大于 corePoolSize ,缓冲队列 workQueue 满,并且线程池中的数量等于maximumPoolSize ,那么通过 handler 所指定的策略来处理此任务。
处理任务的优先级为:核心线程 corePoolSize 、任务队列 workQueue 、最大线程 maximumPoolSize ,如果三者都满了,使用handler 处理被拒绝的任务。当线程池中的线程数量大于 corePoolSize 时,如果某线程空闲时间超过keepAliveTime ,线程将被终止。这样,线程池可以动态的调整池中的线程数。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; public class ThreadExecute { private static final int corePoolSize = 2; // 线程池维护线程的最少数量
private static final int maximumPoolSize = 4; // 线程池维护线程的最大数量
private static final long keepAliveTime = 3; // 线程池维护线程所允许的空闲时间
private static final int PRODUCETASKMAXNUMBER = 10; private static void processMessageTask() {
// 创建等待队列
BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(2);
// 构造一个线程池{这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功}
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
TimeUnit.SECONDS, bqueue,
new ThreadPoolExecutor.DiscardOldestPolicy()); for (int i = 1; i <= PRODUCETASKMAXNUMBER; i++) {
try {
threadPool.execute(new MyThread());
} catch (Exception e) {
System.err.println("thread pool is error, content::" + e);
}
}
} public static void main(String[] args) {
processMessageTask();
}
} // 子类不能比父类抛出更多的异常
class MyThread implements Runnable { @Override
public void run() {
while (true) {
try {
Thread.sleep(2 * 1000);
System.out.println(Thread.currentThread().getName() + "正在执行。。。");
} catch (Exception e) { }
}
}
}
上面的代码,每两秒执行一次,并且线程会一直运行。
总结一下,Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。下面这张图完整描述了线程池的类体系结构:
ExecutorService:真正的线程池接口。
ScheduledExecutorService:能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor:ExecutorService的默认实现。
ScheduledThreadPoolExecutor: 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
当然,new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, handler);用构造方法创建线程池。corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序;LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法。Executors.newCachedThreadPool使用了这个队列。PriorityBlockingQueue:一个具有优先级的无限阻塞队列。maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略:AbortPolicy:直接抛出异常;CallerRunsPolicy:只用调用者所在线程来运行任务;DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务;DiscardPolicy:不处理,丢弃掉;当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务;keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率;TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
最后,说说线程池在项目中的使用。
public interface ThreadPool { /**
* 线程池的初始化
*/
public void init(int poolSize, int watermark); /**
* 将一个可执行的对象推到线程池队列中,在有空闲线程的情况下立刻执行
*/
public boolean schedule(Runnable runnable); /**
* 关闭线程池,调用此方法将放弃所有未执行都已经在线程队列中的Action
*/
public void close();
}
定义了接口ThreadPool,并且提供了三个方法:初始化方法、执行方法、关闭方法。
public class BlockedThreadPoolExecutor extends ThreadPoolExecutor { // 利用Semaphore实现的带阻塞的ThreadPoolExecutor
private Semaphore semaphore = null; public BlockedThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, int watermark){
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.semaphore = new Semaphore(watermark); } @Override
protected void afterExecute(Runnable r, Throwable t) {
semaphore.release();
super.afterExecute(r, t); } @Override
public void execute(Runnable command) {
try {
semaphore.acquire();
super.execute(command);
} catch (InterruptedException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
BlockedThreadPoolExecutor实现了带阻塞的ThreadPoolExecutor。
public class BlockedThreadPoolImpl implements ThreadPool { protected ExecutorService pool; @Override
public void init(int poolSize, int watermark) {
this.pool = new BlockedThreadPoolExecutor(poolSize, poolSize, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(), watermark);
} @Override
public boolean schedule(Runnable runnable) {
boolean ret = false;
try {
pool.execute(runnable);
ret = true; } catch (Throwable t) {
System.out.println(t.getMessage() + t);
}
return ret;
} @Override
public void close() {
// 禁止新的线程从入口进入
pool.shutdown();
try {
if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
// 取消当前正在运行的线程
pool.shutdownNow();
// 等待取消的线程回应
if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
System.err.println("ThreadPoolImpl did not terminate!");
}
}
} catch (InterruptedException ie) {
// 如果遇到异常,重新尝试停止线程
pool.shutdownNow();
// 中断当前线程
Thread.currentThread().interrupt();
}
}
}
BlockedThreadPoolImpl实现了接口ThreadPool,并对init、schedule、close方法进行了实现。
为什么要使用线程池?在Java中,如果每当一个请求到达就创建一个新线程,开销是相当大的。在实际使用中,每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在实际处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个JVM中创建太多的线程,可能会导致系统由于过度消耗内存或者“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务,这就是“池化资源”技术产生的原因。线程池主要用来解决线程生命周期开销问题和资源不足问题,通过对多个任务重用线程,线程创建的开销被分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟。这样,就可以立即请求服务,使应用程序响应更快。另外,通过适当的调整线程池中的线程数据可以防止出现资源不足的情况。
Executors类常用的静态方法有哪些?Executors类里面提供了一些静态工厂,生成一些常用的线程池:newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
//2016-07-25 01:00:24,411 ERROR taobao.hsf -HSF-Provider HSFthread pool is full. //2016-07-258 01:00:24,485 ERROR taobao.hsf -HSF-Provider HSF thread pool is full. //2016-07-25 01:00:24,644 ERROR taobao.hsf -HSF-Provider HSF thread pool is full. //2016-07-25 01:00:24,889 ERROR taobao.hsf -HSF-Provider HSF thread pool is full.
线程池满了,该如何解决?可以看到大量的pool full,从错误可以看出是hsf provider线程被占满(HSF默认线程池数量为600),可以定位出是外部调用tripwb系统的hsf服务所致。HSF会在出现pool full的同时,打印出堆栈到 /home/admin/hsf/HSF_JStack.log。这次出的问题,就能看出是调用httpclient这块出问题了,并且给出咱们出问题的类了,Review AMapUtil.java 果然发现是调用高德地图由于httpclient 超时出现线程的阻塞,由于代码没有设置timeout,线程就一直卡在这,而一直累积直到超过600,系统就崩溃了。
当在使用线程的场景处,当不确定线程数的时候,尽量使用线程池。因此,针对受限的资源(线程,文件,数据库链接等),在使用时需要加以限制,如使用线程池限制线程数,数据库连接池限制链接数。当代码中执行一个批量操作,由于每次执行的时间较长,因此每次执行时都创建一个新线程异步执行。但在高访问下,该代码将会导致线程数过多。合理的方式时使用一个线程池对线程加以限制,当线程池耗尽时拒绝新启线程。
多线程的另一处使用场景,是用来处理各种socket请求。由于Runnable接口中只提供了一个不带返回值run方法,因此当任务需要返回值时就不能满足需求了,于是出现了ExecutorService,这个接口继承了Executor,对提交任务的接口进行了扩展,引入了Callable接口,该接口定义如下:
public interface Callable<V> {
V call() throws Exception;
}
同时接口将任务执行过程进行管理,分为三个状态,提交,shutdown,terminate。
线程池深入(li)的更多相关文章
- 死磕 java线程系列之线程池深入解析——普通任务执行流程
(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:线程池源码部分如无特殊说明均指ThreadPoolExecutor类. 简介 前面我们一起学习了Java中 ...
- Java并发编程与技术内幕:线程池深入理解
摘要: 本文主要讲了Java当中的线程池的使用方法.注意事项及其实现源码实现原理,并辅以实例加以说明,对加深Java线程池的理解有很大的帮助. 首先,讲讲什么是线程池?照笔者的简单理解,其实就是一组线 ...
- Java线程池深入理解
之前面试baba系时遇到一个相对简单的多线程编程题,即"3个线程循环输出ADC",自己答的并不是很好,深感内疚,决定更加仔细的学习<并发编程的艺术>一书,到达掌握的强度 ...
- 转:Java并发编程与技术内幕:线程池深入理解
版权声明:本文为博主林炳文Evankaka原创文章,转载请注明出处http://blog.csdn.net/evankaka 目录(?)[+] ); } catch (InterruptedExcep ...
- JUC线程池深入刨析
JDK默认提供了四种线程池:SingleThreadExecutor.FiexdThreadPool.CachedThreadPool.ScheduledThreadPoolExecutor. 本文会 ...
- 原创:ThreadPoolExecutor线程池深入解读(一)----原理+应用
本文档,适合于对多线程有一定基础的开发人员.对多线程的一些基础性的解读,请参考<java并发编程>的前5章. 对于源代码的解读,本人认为可读可不读.如果你想成为一位顶级的程序员,那就培养自 ...
- 死磕 java线程系列之线程池深入解析——体系结构
(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 简介 Java的线程池是块硬骨头,对线程池的源码做深入研究不仅能提高对Java整个并发编程的理解,也能提高自己 ...
- 死磕 java线程系列之线程池深入解析——生命周期
(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:线程池源码部分如无特殊说明均指ThreadPoolExecutor类. 简介 上一章我们一起重温了下线程的 ...
- 死磕 java线程系列之线程池深入解析——未来任务执行流程
(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:线程池源码部分如无特殊说明均指ThreadPoolExecutor类. 简介 前面我们一起学习了线程池中普 ...
- 死磕 java线程系列之线程池深入解析——定时任务执行流程
(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:本文基于ScheduledThreadPoolExecutor定时线程池类. 简介 前面我们一起学习了普通 ...
随机推荐
- Wireshark抓包分析/TCP/Http/Https及代理IP的识别
前言 坦白讲,没想好怎样的开头.辗转三年过去了.一切已经变化了许多,一切似乎从没有改变. 前段时间调研了一次代理相关的知识,简单整理一下分享之.如有错误,欢迎指正. 涉及 Proxy IP应用 原理/ ...
- 在Github上搭建自己的博客(Windows平台)
折腾了好久,终于在Github上搭建了自己的博客.这里面总结一下过程希望对大家能有所帮助. Github建博优缺点 和 csdn,新浪,网易相比,在Github上可以自己实现功能 和阿里云,VPS相比 ...
- eclipse中的javac命令与java命令
一.eclipse的javac命令:当eclipse对.java(源文件)文件进行保存操作时(快捷键ctrl+s),会执行javac命令.见上图,Default output folder(默认输出文 ...
- Highchart.js
Highcharts所有的源代码及例子均可通过官网下载得到.初学者只需要简单的修改官方提供的例子即可了解Highcharts. 下载 最新版本:Highcharts 3.0.10.Highstock ...
- 深入理解脚本化CSS系列第四篇——脚本化样式表
× 目录 [1]CSSStyleSheet [2]CSSRule 前面的话 关于脚本化CSS,查询样式时,查询的是计算样式:设置单个样式时,设置的是行间样式:设置多个样式时,设置的是CSS类名.脚本化 ...
- 深入学习jQuery选择器系列第二篇——过滤选择器之子元素选择器
× 目录 [1]通用形式 [2]反向形式 [3]首尾元素 [4]唯一元素 前面的话 在上一篇中已经介绍过基础选择器和层级选择器,本文开始介绍过滤选择器.过滤选择器是jQuery选择器中最为庞大也是最为 ...
- 【开源】OSharp3.3框架解说系列:重新开源及3.3版本新特性
OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...
- 【开源】OSharp框架解说系列(6.1):日志系统设计
OSharp是什么? OSharp是个快速开发框架,但不是一个大而全的包罗万象的框架,严格的说,OSharp中什么都没有实现.与其他大而全的框架最大的不同点,就是OSharp只做抽象封装,不做实现.依 ...
- JS+CSS3实现带预览图幻灯片效果
这个案例学习起来还有点吃力,目前还没有独自自己写出来过,贴出来以免忘记. 慕课网该课程原地址:http://www.imooc.com/learn/412 源码: <!DOCTYPE html& ...
- android标题栏下面弹出提示框(一) TextView实现,带动画效果
产品经理用的是ios手机,于是android就走上了模仿的道路.做这个东西也走了一些弯路,写一篇博客放在这里,以后自己也可用参考,也方便别人学习. 弯路: 1.刚开始本来用PopupWindow去实现 ...