前言:最近在做分布式海量数据处理项目,使用到了java的线程池,所以搜集了一些资料对它的使用做了一下总结和探究,

前面介绍的东西大多都是从网上搜集整理而来。文中最核心的东西在于后面两节无界队列线程池和有界队列线程池的实例

使用以及线上问题处理方案。

1.  为什么要用线程池?

在Java中,如果每当一个请求到达就创建一个新线程,开销是相当大的。在实际使用中,每个请求创建新线程的服务器

在创建和销毁线程上花费的时间和消耗的系统资源,甚至可能要比花在实际处理实际的用户请求的时间和资源要多的多。除

了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个JVM中创建太多的线程,可能会导致系统由于

过度消耗内存或者“切换过度”而导致系统资源不足。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻

处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象

来进行服务,这就是“池化资源”技术产生的原因。

线程池主要用来解决线程生命周期开销问题和资源不足问题,通过对多个任务重用线程,线程创建的开销被分摊到多个任

务上了,而且由于在请求到达时线程已经存在,所以消除了创建所带来的延迟。这样,就可以立即请求服务,使应用程序响

应更快。另外,通过适当的调整线程池中的线程数据可以防止出现资源不足的情况。

网上找来的这段话,清晰的描述了为什么要使用线程池,使用线程池有哪些好处。工程项目中使用线程池的场景比比皆是。

本文关注的重点是如何在实战中来使用好线程池这一技术,来满足海量数据大并发用户请求的场景。

2. ThreadPoolExecutor类

Java中的线程池技术主要用的是ThreadPoolExecutor 这个类。先来看这个类的构造函数,

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,

BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

    corePoolSize       线程池维护线程的最少数量

    maximumPoolSize    线程池维护线程的最大数量 

    keepAliveTime      线程池维护线程所允许的空闲时间  

    workQueue          任务队列,用来存放我们所定义的任务处理线程

    threadFactory      线程创建工厂

    handler            线程池对拒绝任务的处理策略

ThreadPoolExecutor 将根据 corePoolSize和 maximumPoolSize 设置的边界自动调整池大小。当新任务在方法

execute(Runnable) 中提交时, 如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是

空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。 如果设置的

corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。

ThreadPoolExecutor是Executors类的实现Executors类里面提供了一些静态工厂,生成一些常用的线程池,主

要有以下几个:

newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行

所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任

务的提交顺序执行。

newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线

程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分

空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池

大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

在实际的项目中,我们会使用得到比较多的是newFixedThreadPool,创建固定大小的线程池,但是这个方法在真实的线上

环境中还是会有很多问题,这个将会在下面一节中详细讲到。

当任务源源不断的过来,而我们的系统又处理不过来的时候,我们要采取的策略是拒绝服务。RejectedExecutionHandler接

口提供了拒绝任务处理的自定义方法的机会。在ThreadPoolExecutor中已经包含四种处理策略。

1)CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

if (!e.isShutdown()) {

r.run();

}

}

这个策略显然不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。

2)AbortPolicy处理程序遭到拒绝将抛出运行时 RejectedExecutionException

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

throw new RejectedExecutionException();

}

这种策略直接抛出异常,丢弃任务。

3)DiscardPolicy不能执行的任务将被删除

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}

这种策略和AbortPolicy几乎一样,也是丢弃任务,只不过他不抛出异常。

4)DiscardOldestPolicy如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,

则重复此过程)

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

if (!e.isShutdown()) {

e.getQueue().poll();

e.execute(r);

}

}

该策略就稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务。这个策略

需要适当小心。

3.  ThreadPoolExecutor无界队列使用

public class ThreadPool {

private final static String poolName = "mypool";

static private ThreadPool threadFixedPool = new ThreadPool(2);

private ExecutorService executor;

static public ThreadPool getFixedInstance() {

return threadFixedPool;

}

private ThreadPool(int num) {

executor = Executors.newFixedThreadPool(num, new DaemonThreadFactory(poolName));

}

public void execute(Runnable r) {

executor.execute(r);

}

public static void main(String[] params) {

class MyRunnable implements Runnable {

public void run() {

System.out.println("OK!");

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

for (int i = 0; i < 10; i++) {

ThreadPool.getFixedInstance().execute(new MyRunnable());

}

try {

Thread.sleep(2000);

System.out.println("Process end.");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

在这段代码中,我们发现我们用到了Executors.newFixedThreadPool()函数,这个函数的实现是这样子的:

return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());

它实际上是创建了一个无界队列的固定大小的线程池。执行这段代码,我们发现所有的任务都正常处理了。但是在真实的线上环

境中会存在这样的一个问题,前端的用户请求源源不断的过来,后端的处理线程如果处理时间变长,无法快速的将用户请求处理

完返回结果给前端,那么任务队列中将堵塞大量的请求。这些请求在前端都是有超时时间设置的,假设请求是通过套接字过来,

当我们的后端处理进程处理完一个请求后,从队列中拿下一个任务,发现这个任务的套接字已经无效了,这是因为在用户端已经

超时,将套接字建立的连接关闭了。这样一来我们这边的处理程序再去读取套接字时,就会发生I/0 Exception. 恶性循环,导致我

们所有的处理服务线程读的都是超时的套接字,所有的请求过来都抛I/O异常,这样等于我们整个系统都挂掉了,已经无法对外提供

正常的服务了。

对于海量数据的处理,现在业界都是采用集群系统来进行处理,当请求的数量不断加大的时候,我们可以通过增加处理节点,反正现

在硬件设备相对便宜。但是要保证系统的可靠性和稳定性,在程序方面我们还是可以进一步的优化的,我们下一节要讲述的就是针对

线上出现的这类问题的一种处理策略。

4.   ThreadPoolExecutor有界队列使用

public class ThreadPool {

private final static String poolName = "mypool";

static private ThreadPool threadFixedPool = null;

public ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(2);

private ExecutorService executor;

static public ThreadPool getFixedInstance() {

return threadFixedPool;

}

private ThreadPool(int num) {

executor = new ThreadPoolExecutor(2, 4,60,TimeUnit.SECONDS, queue,new DaemonThreadFactory

(poolName), new ThreadPoolExecutor.AbortPolicy());

}

public void execute(Runnable r) {

executor.execute(r);

}

public static void main(String[] params) {

class MyRunnable implements Runnable {

public void run() {

System.out.println("OK!");

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

int count = 0;

for (int i = 0; i < 10; i++) {

try {

ThreadPool.getFixedInstance().execute(new MyRunnable());

} catch (RejectedExecutionException e) {

e.printStackTrace();

count++;

}

}

try {

log.info("queue size:" + ThreadPool.getFixedInstance().queue.size());

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("Reject task: " + count);

}

}

首先我们来看下这段代码几个重要的参数,corePoolSize 为2,maximumPoolSize为4,任务队列大小为2,每个任务平

均处理时间为10ms,一共有10个并发任务。

执行这段代码,我们会发现,有4个任务失败了。这里就验证了我们在上面提到有界队列时候线程池的执行顺序。当新任务在

方法 execute(Runnable) 中提交时, 如果运行的线程少于 corePoolSize,则创建新线程来处理请求。 如果运行的线程多于

corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程,如果此时线程数量达到maximumPoolSize,并且队

列已经满,就会拒绝继续进来的请求。

现在我们调整一下代码中的几个参数,将并发任务数改为200,执行结果Reject task: 182,说明有18个任务成功了,线程处理

完一个请求后会接着去处理下一个过来的请求。在真实的线上环境中,会源源不断的有新的请求过来,当前的被拒绝了,但只要线

程池线程把当下的任务处理完之后还是可以处理下一个发送过来的请求。

通过有界队列可以实现系统的过载保护,在高压的情况下,我们的系统处理能力不会变为0,还能正常对外进行服务,虽然有些服

务可能会被拒绝,至于如何减少被拒绝的数量以及对拒绝的请求采取何种处理策略我将会在下一篇文章《系统的过载保护》中继续

阐述。

参考文献:

  1. ThreadPoolExecutor使用与思考(上)-线程池大小设置与BlockedQueue的三种实现区别 http://dongxuan.iteye.com/blog/901689
  2. ThreadPoolExecutor使用与思考(中)-keepAliveTime及拒绝策略http://dongxuan.iteye.com/blog/902571
  3. ThreadPoolExecutor源代码
  4. Java线程池介绍以及简单实例 http://wenku.baidu.com/view/e4543a7a5acfa1c7aa00cc25.html

转载于:https://www.cnblogs.com/cstar/archive/2012/06/14/2549494.html

大数据处理系列之(一)Java线程池使用相关推荐

  1. Java多线程系列(三):Java线程池的使用方式,及核心运行原理

    之前谈过多线程相关的4种常用Java线程锁的特点,性能比较.使用场景,今天主要分享线程池相关的内容,这些都是属于Java面试的必考点. 为什么需要线程池 java中为了提高并发度,可以使用多线程共同执 ...

  2. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(一)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

  3. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(二)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

  4. Java面试系列之并发编程专题-Java线程池灵魂拷问

    金三银四跳槽季即将来临,想必有些猿友已经蠢蠢欲动在做相关的准备了!在接下来的日子里,笔者将坚持写作.分享Java工程师在面试求职期间的方方面面,包括简历制作.面试场景复现.面试题解答.谈薪技巧 以及 ...

  5. 面试必问---Java线程池8大拒绝策略

    前言 谈到java的线程池最熟悉的莫过于ExecutorService接口了,jdk1.5新增的java.util.concurrent包下的这个api,大大的简化了多线程代码的开发.而不论你用Fix ...

  6. Java 线程池必知的8 大拒绝策略

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | kailing.pub/article/ind ...

  7. java 线程池 初始大小_为什么tomcat的默认线程池大小如此之大? - java

    我注意到默认的tomcat 7线程池大小似乎是200. 但是普通的CPU似乎有16个内核. 因此只能并行执行16个线程 为什么tomcat使用那么多线程. 参考方案 多年以来,许多单核计算机问世,并且 ...

  8. 【Java】Java 线程池 8 大拒绝策略

    1.概述 谈到java的线程池最熟悉的莫过于ExecutorService接口了,jdk1.5新增的java.util.concurrent包下的这个api,大大的简化了多线程代码的开发.而不论你用F ...

  9. java线程池_Java 线程池 8 大拒绝策略,面试必问!

    点击上方 Java后端,选择设为星标 技术博文,及时送达 前言 谈到java的线程池最熟悉的莫过于ExecutorService接口了,jdk1.5新增的java.util.concurrent包下的 ...

最新文章

  1. LIC-Fusion 2.0:基于滑动窗口法平面特征跟踪的激光雷达惯性相机里程计
  2. (2) ebj学习:hello world入门案例
  3. 南京二本有什么计算机学校,南京有什么好的二本学校?
  4. python连接数据库设置编码_python操作mysql中文显示乱码的解决方法
  5. bzoj:2331: [SCOI2011]地板
  6. react 表单设计器_神器:让你相见恨晚的5个React应用程序库
  7. cad自动填写页码lisp_CAD图纸页码的自动生成-农夫也玩CAD
  8. 自定义报表(demo1)
  9. Android开发好用的依赖库
  10. python使用matplotlib可视化线图(line plot)、使用semilogy函数将Y轴数据处理为对数坐标(logarithmic scale in Matplotlib)
  11. 用户行为分析 无埋点代码
  12. 如何设置U盘为第一启动项,在安装windows操作系统时如何从U盘启动?
  13. 从 Chrome 源码看浏览器如何计算 CSS
  14. 研发管理进阶:边怼人边改进
  15. H5实时上传位置定位 pc生成轨迹;h5保持后台运行
  16. video.js 视频播放插件使用
  17. jquery slideToggle 动画问题
  18. 牛客--剑指offer,JZ3,JZ4,JZ5,JZ6
  19. As-Projective-As-Possible Image Stitching with Moving DLT阅读笔记
  20. 如何用解耦合提升开发效率?闲鱼团队有了新发现

热门文章

  1. Xilinx Platform Cable USB II 下载器驱动安装教程——Win10
  2. 神经网络 | BP神经网络-数字识别(附源代码)
  3. 北斗导航 | NED(北东地)转ECEF(地心地固)或ECEF转NED(Matlab源代码)
  4. 国外计算机科学英语演讲,2014年暨大英语演讲大赛圆满落幕
  5. 华为云服务器linux切换账号,华为云Windows服务器如何切换为Linux系统?
  6. 详解数据挖掘与机器学习的区别与联系
  7. Windows下C/C++获取当前系统时间
  8. 电子科技大学研究生计算机与科学,川大和电子科技大学那个计算机考研专业好?...
  9. java 定时器代码_Java定时器代码的编写
  10. bootstrap 右对齐样式_Bootstrap的文本处理