java并发编程实战:第八章----线程池的使用
一、在任务和执行策略之间隐性耦合
Executor框架将任务的提交和它的执行策略解耦开来。虽然Executor框架为制定和修改执行策略提供了相当大的灵活性,但并非所有的任务都能适用所有的执行策略。
- 依赖性任务:依赖其他同步任务的结果,使其不得不顺序执行,影响活跃性
- 使用线程封闭的任务:在单线程的Executor中执行,任务可以不是线程安全的,但是一旦提交到线程池时,就会失去线程安全
- 对响应时间敏感的任务:在单个线程或含有少量线程的线程池中执行是不可接受的
- 使用ThreadLocal的任务:ThreadLocal使每个线程都可以拥有某个变量的一个私有"版本",而线程池中的线程是重复使用的,即一次使用完后,会被重新放回线程池,可被重新分配使用。因此,ThreadLocal线程变量,如果保存的信息只是针对一次请求的,放回线程池之前需要清空这些Threadlocal变量的值(或者取得线程之后,首先清空这些Threadlocal变量的值)
只有任务都是同类型并且相互独立时,线程池的效率达到最佳
1、线程饥饿死锁——在线程池中所有正在执行任务的线程都由于等待其他仍处于工作队列中的任务而阻塞
例1:在单线程池中,正在执行的任务阻塞等待队列中的某个任务执行完毕
例2:线程池不够大时,通过栅栏机制协调多个任务时
例3:由于其他资源的隐性限制,每个任务都需要使用有限的数据库连接资源,那么不管线程池多大,都会表现出和和连接资源相同的大小
每当提交了一个有依赖性的Executor任务时,要清楚地知道可能会出现线程"饥饿"死锁,因此需要在代码或配置Executor地配置文件中记录线程池地大小限制或配置限制
2、运行时间较长的任务
线程池的大小应该超过有较长执行时间的任务数量,否则可能造成线程池中线程均服务于长时间任务导致其它短时间任务也阻塞导致性能下降
缓解策略:限定任务等待资源的时间,如果等待超时,那么可以把任务标示为失败,然后中止任务或者将任务重新返回队列中以便随后执行。这样,无论任务的最终结果是否成功,这种方法都能确保任务总能继续执行下去,并将线程释放出来以执行一些能更快完成的任务。例如Thread.join、BlockingQueue.put、CountDownLatch.await以及Selector.select等
二、设置线程池的大小
线程池的理想大小取决于被提交任务的类型及所部署系统的特性
- 线程池过大,那么大量的线程将在相对很少的CPU和内存资源上发生竞争,这不仅会导致更高的内存使用量,而且还可能耗尽资源
- 如果线程池过小,那么将导致许多空闲的处理器无法执行工作,从而降低吞吐量
对于计算密集型的任务,在拥有Ncpu个处理器的系统上,当线程池的大小为Ncpu+1时,通常能实现最优的利用率;对于包含I/O操作或者其他阻塞操作的任务,由于线程并不会一直执行,因此线程池的规模应该更大
N(threads)=N(cpu)*U(cpu)*(1+W/C) N(cpu)=CPU的数量=Runtime.getRuntime().availableProcessors(); U(cpu)= 期望CPU的使用率,0<=U(cpu)<=1 ;W/C=等待时间与运行时间的比率
三、配置ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
1、线程的创建与销毁
- CorePoolSize: 线程池基本大小,在创建ThreadPoolExecutor初期,线程并不会立即启动,而是等到有任务提交时才会启动,除非调用prestartAllCoreThreads,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。
- MaxmumPooSize: 线程池最大大小表示可同时活动的线程数量的上限。若某个线程的空闲时间超过了keepAliveTime, 则被标记为可回收的
newFixedThreadPool: CorePoolSize = MaxmumPoolSize
newCachedThreadPool: CorePoolSize=0,MaxmumPoolSize=Integer.MAX_VALUE,线程池可被无限扩展,需求降低时自动回收
2、管理队列任务
- workQueue:用于保存超过线程池线程处理速率的Runnable任务的队列 (三种:无界队列、有界队列和同步移交)
newFixedThreadPool和newSingleThreadPool在默认情况下将使用一个无界的LinkedBlockingQueue,有更好的性能
使用有界队列有助于避免资源耗尽的情况发生,为了避免当队列填满后,在使用有界的工作队列时,队列的大小与线程池的大小必须一起调节,能防止过载
对于非常大的或者无界的线程池,可以通过使用SynchronousQueue来避免任务排队,要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接受这个元素,任务会直接移交给执行它的线程,否则将拒绝任务。newCachedThreadPool工厂方法中就使用了SynchronousQueue
使用优先队列PriorityBlockingQueue可以控制任务被执行的顺序
3、饱和策略
- AbortPolicy(中止策略),默认的饱和策略。会抛出RejectedExecutionException异常(抛弃当前任务vs抛弃最旧任务)
- 调用者运行:下一个任务在调用了execute方法的主线程中进行运行,主线程至少在一段时间内不能提交任何任务。到达的请求将被保存在TCP层的队列中而不是在应用程序的队列中,导致服务器在高负载下实现一种平缓的性能降低
其他:对执行策略进行修改,使用信号量,控制处于执行中的任务
public class BoundedExecutor {
private final Executor exec;
private final Semaphore semaphore; public BoundedExecutor(Executor exec, int bound) {
this.exec = exec;
this.semaphore = new Semaphore(bound);
} public void submitTask(final Runnable command){
try {
semaphore.acquire(); //提交任务前请求信号量
exec.execute(new Runnable() {
@Override
public void run() {
try{
command.run();
} finally{
semaphore.release(); //执行完释放信号
}
}
});
} catch (InterruptedException e) {
// handle exception
}
}
}
4、线程工厂
通过自定义线程工厂可以对其进行扩展加入新的功能实现
当应用需要利用安全策略来控制某些特殊代码库的访问权,可以利用PrivilegedThreadFactory来定制自己的线程工厂,以免出现安全性异常。将与创建privilegedThreadFactory的线程拥有相同的访问权限、AccessControlContext和contextClassLoader
自定义线程工厂
public class MyThreadFactory implements ThreadFactory {
private final String poolName; public MyThreadFactory(String poolName) {
super();
this.poolName = poolName;
} @Override
public Thread newThread(Runnable r) {
return new MyAppThread(r);
}
} public class MyAppThread extends Thread {
public static final String DEFAULT_NAME="MyAppThread";
private static volatile boolean debugLifecycle = false;
private static final AtomicInteger created = new AtomicInteger();
private static final AtomicInteger alive = new AtomicInteger();
private static final Logger log = Logger.getAnonymousLogger(); public MyAppThread(Runnable r) {
this(r, DEFAULT_NAME);
} public MyAppThread(Runnable r, String name) {
super(r, name+ "-" + created.incrementAndGet());
setUncaughtExceptionHandler( //设置未捕获的异常发生时的处理器
new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
log.log(Level.SEVERE, "UNCAUGHT in thread " + t.getName(), e);
}
});
} @Override
public void run() {
boolean debug = debugLifecycle;
if (debug)
log.log(Level.FINE, "running thread " + getName());
try {
alive.incrementAndGet();
super.run();
} finally {
alive.decrementAndGet();
if (debug)
log.log(Level.FINE, "existing thread " + getName());
}
}
}
5、在调用构造函数后在定制ThreadPoolExecutor
- 可以在创建线程池后,再通过Setter方法设置其基本属性(将ExecutorService扩展为ThreadPoolExecutor)
- 在Executors中包含一个unconfigurableExecutorService工厂方法,该方法对一个现有的ExecutorService进行包装,使其只暴露出ExecutorService的方法,因此不能对它进行配置
四、扩展ThreadPoolExecutor
ThreadPoolExecutor使用了模板方法模式,提供了beforeExecute、afterExecute和terminated扩展方法
- 线程执行前调用beforeExecute(如果beforeExecute抛出了一个RuntimeException,那么任务将不会被执行)
- 线程执行后调用afterExecute(抛出异常也会调用,如果任务在完成后带有一个Error,那么就不会调用afterExecute)
- 在线程池完成关闭操作时调用terminated,也就是所有任务都已经完成并且所有工作者线程也已经关闭后
增加日志和记时等功能的线程池
public class TimingThreadPoolExecutor extends ThreadPoolExecutor {
private final ThreadLocal<Long> startTime = new ThreadLocal<Long>();//任务执行开始时间
private final Logger log = Logger.getAnonymousLogger();
private final AtomicLong numTasks = new AtomicLong(); //统计任务数
private final AtomicLong totalTime = new AtomicLong(); //线程池运行总时间 public TimingThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
} @Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
log.fine(String.format("Thread %s: start %s", t, r));
startTime.set(System.nanoTime());
} @Override
protected void afterExecute(Runnable r, Throwable t) {
try{
long endTime = System.nanoTime();
long taskTime = endTime - startTime.get();
numTasks.incrementAndGet();
totalTime.addAndGet(taskTime);
log.fine(String.format("Thread %s: end %s, time=%dns", t, r, taskTime));
} finally{
super.afterExecute(r, t);
}
} @Override
protected void terminated() {
try{
//任务执行平均时间
log.info(String.format("Terminated: average time=%dns", totalTime.get() / numTasks.get()));
}finally{
super.terminated();
}
}
}
五、递归算法的并行化
- 如果循环中的迭代操作都是独立的,并且不需要等待所有的迭代操作都完成再继续执行,那么就可以使用Executor将串行循环转化为并行循环
- 如果需要提交一个任务集并等待它们完成,那么可以使用ExecutorService.invokeAll
- 如果递归执行的任务中,在每个迭代操作中都不需要来自于后续递归迭代的结果,可以创建一个特定于遍历过程的Executor,并使用shutdown和awaitTermination等方法,等待上面并行运行的结果
java并发编程实战:第八章----线程池的使用的更多相关文章
- Java 并发编程——Executor框架和线程池原理
Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...
- [Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors
[Java并发编程(二)] 线程池 FixedThreadPool.CachedThreadPool.ForkJoinPool?为后台任务选择合适的 Java executors ... 摘要 Jav ...
- [Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ...
[Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ... 摘要 介绍 Java 并发包里的几个主要 ExecutorService . 正文 ...
- Java 并发编程——Executor框架和线程池原理
Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...
- Java并发编程、多线程、线程池…
<实战java高并发程序设计>源码整理https://github.com/petercao/concurrent-programming/blob/master/README.md Ja ...
- Java并发编程之深入理解线程池原理及实现
Java线程池在实际的应用开发中十分广泛.虽然Java1.5之后在JUC包中提供了内置线程池可以拿来就用,但是这之前仍有许多老的应用和系统是需要程序员自己开发的.因此,基于线程池的需求背景.技术要求了 ...
- Java并发编程(08):Executor线程池框架
本文源码:GitHub·点这里 || GitEE·点这里 一.Executor框架简介 1.基础简介 Executor系统中,将线程任务提交和任务执行进行了解耦的设计,Executor有各种功能强大的 ...
- Java并发编程(十一)线程池的使用
1.new Thread的弊端如下: a. 每次new Thread新建对象性能差. b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom. c. 缺乏更多 ...
- java并发编程(四) 线程池 & 任务执行、终止源码分析
参考文档 线程池任务执行全过程:https://blog.csdn.net/wojiaolinaaa/article/details/51345789 线程池中断:https://www.cnblog ...
- Java并发编程:4种线程池和缓冲队列BlockingQueue
一. 线程池简介 1. 线程池的概念: 线程池就是首先创建一些线程,它们的集合称为线程池.使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动 ...
随机推荐
- cookies封装
/** * @author wxf */var cookie=new function(){ this.set=function(name,value,hours){ var life=new Dat ...
- mysql安装过程及注意事项
1.1. 下载: 我下载的是64位系统的zip包: 下载地址:https://dev.mysql.com/downloads/mysql/ 下载zip的包: 下载后解压:D:\软件安装包\mysql- ...
- 需登录账号与密码的网页爬取demo
public static String connect(String dataUrl){ String result = null; try { HttpClient httpclient = ne ...
- TNS-12535: TNS:operation timed out、TNS-00505: Operation timed out
在查看alert日志的时候发现: 1 *********************************************************************** 2 3 Fatal ...
- http中的Content-Type
要学习content-type,必须事先知道它到底是什么,是干什么用的. HTTP协议(RFC2616)采用了请求/响应模型.客户端向服务器发送一个请求,请求头包含请求的方法.URI.协议版本.以及包 ...
- selenium+python在mac环境上的搭建
前言 mac自带了python2.7的环境,所以在mac上安装selenium环境是非常简单的,输入2个指令就能安装好 需要安装的软件: 1.pip 2.selenium2.53.6 3.Firefo ...
- 20165233 Java第四章学习总结
20165233 2017-2018-2 <Java程序设计>第三周学习总结 教材学习内容总结 基础 类:包括类声明和类体. 其中类声明的变量被称作对象变量,简称对象. 类体中包括两部分: ...
- tomcat https 启用8443加证书
<?xml version='1.0' encoding='utf-8'?> <!-- Licensed to the Apache Software Foundation (ASF ...
- Android开发:实时处理摄像头预览帧视频------浅析PreviewCallback,onPreviewFrame,AsyncTask的综合应用(转)
原文地址:http://blog.csdn.net/yanzi1225627/article/details/8605061# 很多时候,android摄像头模块不仅预览,拍照这么简单,而是需要在预览 ...
- Unity XLua 官方案例学习
1. Helloworld using UnityEngine; using XLua; public class Helloworld : MonoBehaviour { // Use this f ...