1.为什么要使用线程池?

​ 诸如 Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。请求以某种方式到达服务器,这种方式可能是通过网络协议(例如 HTTP、FTP )、通过 JMS队列或者可能通过轮询数据库。

​ 不管请求如何到达,服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大

的。每当一个请求到达就创建一个新线程,然后在新线程中为请求服务,但是频繁的创建线程,销毁线程所带来的系统开销其实是非常大的。

线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。

​ 这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它

任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

风险与机遇

​ 用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,

诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。

2.创建线程池及其使用

/**
* 线程池Demo
*/
public class ThreadPoolDemo { public static void main(String[] args) {
LinkedBlockingDeque<Runnable> objects = new LinkedBlockingDeque<>(20);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
10,20,3000,TimeUnit.MILLISECONDS,
objects
);
for (int i = 0; i < 100; i++) {
threadPoolExecutor.submit(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
}
}

3.Future与Callable、FutureTask

​ Callable与Runable功能相似,Callable的call有返回值,可以返回给客户端,而Runable没有返回值,一般情

况下,Callable与FutureTask一起使用,或者通过线程池的submit方法返回相应的Future.

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果.

​ FutureTask则是一个RunnableFuture,而RunnableFuture既实现了Runnbale又实现了Futrue这两个接口

public class CallableDemo implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(3000L);
return "1111";
} public static void main(String[] args) throws ExecutionException, InterruptedException { CallableDemo callableDemo = new CallableDemo();
FutureTask<String> stringFutureTask = new FutureTask<>(callableDemo);
new Thread(stringFutureTask).start(); System.out.println(stringFutureTask.get());
} }
/**
* 线程池Demo
*/
public class ThreadPoolDemo { public static void main(String[] args) throws Exception {
LinkedBlockingDeque<Runnable> objects = new LinkedBlockingDeque<>(20);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
10,20,3000,TimeUnit.MILLISECONDS,
objects
); Future<String> submit = null;
// for (int i = 0; i < 100; i++) {
// Future<?> submit = threadPoolExecutor.submit(() -> {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName());
// });
// } // for (int i = 0; i < 100; i++) {
submit = threadPoolExecutor.submit(new CallableDemo());
// } for (int i = 0; i < 100; i++) { System.out.println(submit.get()); } }
}

4.线程池的核心组成部分及其运行机制

corePoolSize:核心线程池大小 cSize

maximumPoolSize:线程池最大容量 mSize

keepAliveTime:当线程数量大于核心时,多余的空闲线程在终止之前等待新任务的最大时间。

unit:时间单位

workQueue:工作队列 nWorks

诸如 Web 服务器、数据库服务器、文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。请求以某种方式到达服务器,这种方式可能是通过网络协议(例如 HTTP、FTP )、通过 JMS队列或者可能通过轮询数据库。不管请求如何到达,服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的。每当一个请求到达就创建一个新线程,然后在新线程中为请求服务,但是频繁的创建线程,销毁线程所带来的系统开销其实是非常大的。

线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就强制其它任何新到的请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。

风险与机遇

用线程池构建的应用程序容易遭受任何其它多线程应用程序容易遭受的所有并发风险,

诸如同步错误和死锁,它还容易遭受特定于线程池的少数其它风险,诸如与池有关的死锁、资源不足和线程泄漏。

ThreadFactory:线程工厂

handler:拒绝策略

运行机制

通过new创建线程池时,除非调用prestartAllCoreThreads方法初始化核心线程,否则此时线程池中有0个线

程,即使工作队列中存在多个任务,同样不会执行

任务数X

x <= cSize 只启动x个线程

x >= cSize && x < nWorks + cSize 会启动 <= cSize 个线程 其他的任务就放到工作队列里

x > cSize && x > nWorks + cSize

x-(nWorks) <= mSize 会启动x-(nWorks)个线程

x-(nWorks) > mSize 会启动mSize个线程来执行任务,其余的执行相应的拒绝策略

5.线程池拒绝策略

AbortPolicy:该策略直接抛出异常,阻止系统正常工作

CallerRunsPolicy:只要线程池没有关闭,该策略直接在调用者线程中,执行当前被丢弃的任务(叫老板帮你

干活)

DiscardPolicy:直接啥事都不干,直接把任务丢弃

DiscardOldestPolicy:丢弃最老的一个请求(任务队列里面的第一个),再尝试提交任务

/**
* 自定义拒绝策略
*/
public class CustomPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("线程池满了!");
}
}
/**
* 线程池Demo
*/
public class ThreadPoolDemo { public static void main(String[] args) throws Exception {
LinkedBlockingDeque<Runnable> objects = new LinkedBlockingDeque<>(30);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
20,20,1000,TimeUnit.MILLISECONDS,
objects,new CustomPolicy()
); Future<String> submit = null;
// for (int i = 0; i < 100; i++) {
// Future<?> submit = threadPoolExecutor.submit(() -> {
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName());
// });
// } for (int i = 0; i < 50; i++) {
submit = threadPoolExecutor.submit(new CallableDemo());
} for (int i = 0; i < 100; i++) { System.out.println(submit.get()); }
}
}

6.Executor框架

通过相应的方法,能创建出6种线程池

ExecutorService executorService = Executors.newCachedThreadPool();

ExecutorService executorService1= Executors.newFixedThreadPool(2);

ScheduledExecutorService scheduledExecutorService =Executors.newScheduledThreadPool(1);

ExecutorService executorService2 =Executors.newWorkStealingPool();

ExecutorService executorService3 =Executors.newSingleThreadExecutor();

ScheduledExecutorService scheduledExecutorService1 =Executors.newSingleThreadScheduledExecutor();

上面的方法最终都创建了ThreadPoolExecutor

newCachedThreadPool:创建一个可以根据需要创建新线程的线程池,如果有空闲线程,优先使用空闲的线

newFixedThreadPool:创建一个固定大小的线程池,在任何时候,最多只有N个线程在处理任务

newScheduledThreadPool:能延迟执行、定时执行的线程池

newWorkStealingPool:工作窃取,使用多个队列来减少竞争

newSingleThreadExecutor:单一线程的线程次,只会使用唯一一个线程来执行任务,即使提交再多的任务,也都是会放到等待队列里进行等待

newSingleThreadScheduledExecutor:单线程能延迟执行、定时执行的线程池

/**
* Executor 框架Demo
*/
public class ExecutorDemo { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool();
ExecutorService executorService1 = Executors.newFixedThreadPool(2);
ExecutorService executorService2 = Executors.newScheduledThreadPool(2);
ExecutorService executorService3 = Executors.newWorkStealingPool(2);
ExecutorService executorService4 = Executors.newSingleThreadExecutor();
ExecutorService executorService5 = Executors.newSingleThreadScheduledExecutor();
//提交要处理的任务
executorService.submit(()->{ System.out.println(Thread.currentThread().getName());
}); executorService.shutdown();
} }

7.线程池的使用建议

尽量避免使用Executor框架创建线程池

newFixedThreadPool newSingleThreadExecutor 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积

大量的请求,从而导致 OOM。

1.编辑cofiguration里面的参数:
-Xms60m
-Xmx60m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=E:\heapError
  1. 运行测试Demo:

    注意一定要保存刚才的配置再运行,这个在启动类的第二个选项栏

    /**
    * 模拟OOM
    */
    public class OOMDemo { public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(2); while (true){
    executorService.submit(()->{ System.out.println(Thread.currentThread().getName()); try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    } });
    } } }
  2. HeapAnalyzer分析文件如下(可以看出主要是阻塞线程中的队列满等待造成的OOM):

第二个例子:newCachedThreadPool newScheduledThreadPool 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM

/**
*这个会把你的所有内存占满后才停止
* 谨慎测试这个
*/
public class OOMDemo2 {
public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); while (true){ executorService.submit(()->{ System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
} });
} } }
为什么第二个例子,在限定了堆的内存之后,还会把整个电脑的内存撑爆

创建线程时用的内存并不是我们制定jvm堆内存,而是系统的剩余内存。(电脑内存-系统其它程序占用的内

存-已预留的jvm内存)

创建线程池时,核心线程数不要过大

相应的逻辑,发生异常时要处理

submit 如果发生异常,不会立即抛出,而是在get的时候,再抛出异常

public class OOMDemo {

    public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2); for (int i = 0; i < 100; i++) {
executorService.submit(()->{ System.out.println(Thread.currentThread().getName()); int j=1/0; });
} } }

get()的时候抛出异常:

for (int i = 0; i < 100; i++) {
Future<?> submit = executorService.submit(() -> { System.out.println(Thread.currentThread().getName()); int j = 1 / 0; });
try {
submit.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}

execute 直接抛出异常

public class OOMDemo {

    public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2); for (int i = 0; i < 100; i++) {
executorService.execute(()->{ System.out.println(Thread.currentThread().getName()); int j=1/0; });
}
}
}

线程池及Executor框架的更多相关文章

  1. 线程池之Executor框架

    线程池之Executor框架 Java的线程既是工作单元,也是执行机制.从JDK5开始,把工作机单元和执行机制分离开来.工作单元包括Runnable和Callable,而执行机制由Executor框架 ...

  2. Java多线程学习(八)线程池与Executor 框架

    目录 历史优质文章推荐: 目录: 一 使用线程池的好处 二 Executor 框架 2.1 简介 2.2 Executor 框架结构(主要由三大部分组成) 2.3 Executor 框架的使用示意图 ...

  3. 线程池:Execution框架

    每问题每线程:在于它没有对已创建线程的数量进行任何限制,除非对客户端能够抛出的请求速率进行限制. 下边 有些图片看不到,清看原地址:http://www.360doc.com/content/10/1 ...

  4. 并发07--线程池及Executor框架

    一.JAVA中的线程池 线程池的实现原理及流程如下图所示: 如上图所示,当一个线程提交到线程池时(execute()或submit()),先判断核心线程数(corePoolSize)是否已满,如果未满 ...

  5. 001-多线程-JUC线程池-线程池架构-Executor、ExecutorService、ThreadPoolExecutor、Executors

    一.概述 1.1.线程池架构图 1. Executor 它是"执行者"接口,它是来执行任务的.准确的说,Executor提供了execute()接口来执行已提交的 Runnable ...

  6. B2. Concurrent 线程池(Executor)

    [概述] 与数据库连接管理类似,线程的创建和销毁会耗费较大的开销,使用 “池化技术” 来更好地利用当前线程资源,减少因线程创建和销毁带来的开销,这就是线程池产生的原因. [无限创建线程的不足] 在生产 ...

  7. Java线程池学习

    Java线程池学习 Executor框架简介 在Java 5之后,并发编程引入了一堆新的启动.调度和管理线程的API.Executor框架便是Java 5中引入的,其内部使用了线程池机制,它在java ...

  8. 《java并发编程实战》读书笔记5--任务执行, Executor框架

    第6章 任务执行 6.1 在线程中执行任务 第一步要找出清晰的任务边界.大多数服务器应用程序都提供了一种自然的任务边界选择方式:以独立的请求为边界. -6.6.1 串行地执行任务 最简单的任务调度策略 ...

  9. Java并发编程-Executor框架(转)

    本文转自http://blog.csdn.net/chenchaofuck1/article/details/51606224 感谢作者 我们在传统多线程编程创建线程时,常常是创建一些Runnable ...

随机推荐

  1. MSSQL随机修改时间

    GO DECLARE @BDate datetime, @EDate datetime SET @BDate = '20190801' --上限 SET @EDate = '20190819 23:5 ...

  2. ln -s 使用

    最近开发项目中遇到一个问题,网站上传文件到项目根目录下的upload文件夹,但是每次项目发布都会把upload文件夹删除掉,所以我们需要把upload文件夹放在系统目录下而不是项目根目录下. 访问的时 ...

  3. HDU 6212 Zuma

    Zuma 这个题没有素质!它卡常! 我发现网上很多人的题解都写得很奇怪,也不好确定正确性,所以我借这篇题解表达一下愚见 定义$ dp[i][j][0...4]$表示 0:消完了 1:还剩1个0 2:还 ...

  4. 为什么不要使用==比较Integer?

    比较Integer的时候,不要用==. 查看Integer的源码,如下: /** * Returns an {@code Integer} instance representing the spec ...

  5. Transform the vot dataset into 4 corner format

    Transform the vot dataset into 4 corner format Matlab code to change the 8 value ground truth into 4 ...

  6. Camtasia如何录制小文件视频

      Camtasia 录制设置   FrameRate设成4就行,音频格式:PCM, 8000Hz, 8 位, 单声道, 7KB/秒 ,这样更小.   文章来源:刘俊涛的博客 欢迎关注公众号.留言.评 ...

  7. hive分区与实际分区文件不匹配导致spark读文件出错的问题解决

    先解释下,由于历史原因导致hive中的看到分区比hdfs中的文件夹不匹配,存在hive中分区数有,实际hdfs中无此文件夹. spark中通过sparkSQL读取hive中的该表时,将会出现异常. 解 ...

  8. 阿里巴巴Java开发手册(华山版)

    插件下载地址: https://github.com/alibaba/p3c 2018年9月22日,在2018杭州云栖大会上,召开<码出高效:Java 开发手册>新书发布会,并宣布将图书所 ...

  9. Centos7安装完成后设定基本的网络配置

    Centos7设定网络 新安装的centos7,网络默认是不启动的,需要人为的手工修改配置文件,在这里把这个过程简要的记录一下. 设定ip地址与mac地址自定义 [root@web ~]# cd /e ...

  10. ThinkPHP 5.0.x、5.1.x、5.2.x 全版本远程命令执行漏洞

    ThinkPHP 5.0.x.5.1.x.5.2.x 全版本远程代码执行漏洞 作者:SoulCat. 来源:CSDN 原文:https://blog.csdn.net/csacs/article/de ...