定义
线程池就是管理一大堆线程行为的资源池,包括创建,销毁,执行。
优势
开销更小: 线程池比每个任务分配一个线程开销更小,它避免了创建销毁的开销。
响应更快: 在一般情况下,线程池中的线程就已经存在了。避免了创建的过程,所以响应更快。
资源利用更高: 线程池,能让资源更充分的利用。线程不是越多越好,适中数量才能让资源利用更高。不然,CPU轮询可能就够吃一壶了。
更稳定: 线程池能避免资源竞争,资源包括CPU,内存,也避免了内存耗尽
通过Executor可创建的类型
newFixedThreadPool: 固定长度,达到线程池最大数量就不再创新新线程。新增任务会根据policy抛出exception或忽略或返回
newCachedThreadPool: 不固定长度,会及时回收空闲线程,或者增加新线程
newSingleThreadExecutor: 单线程,确保任务按照某个顺序执行
newScheduledThreadPool: 固定长度,可以定时或延迟执行。多了一个delay, atTime
多线程环境存在的状态
1. 多线程最好的情况是 线程间都是独立的。
2. 线程间有依赖关系,必须要有先后执行顺序。一个线程的结果是另一个线程的某个计算因子
3. 吞吐率有要求,比如为了达到更好的体验,线程必须在多长时间内做出响应。线程必须要在尽可能短的时间内处理更多的任务。
4. 线程运行时间的不同,可能会造成资源耗尽,堵塞。不同类型的任务,最好安排的不同的线程池中,保证其运行时间的一致
针对性的策略
线程间有依赖--------->尽量保证线程池够大。这能减少线程拥塞。
线程运行任务的时间不一----------->将这些任务类型划分安排到不同的线程池中去
线程池数量确定------------->根据当前的CPU,CPU使用率,任务资源消耗,系统配置,确定允许的线程并发数量
运行时间较长--------->线程运行的时间长了,轻则影响响应,重则阻塞,甚至死锁。一般情况下,最起码要有一个超时设置,再优化算法之类。
若线程池的线程经常被阻塞,那么可以考虑下是否 线程池太小了。
线程池大小计算公式
N:线程池的大小
C:CPU数量
U:CPU利用率
X:线程等待时间/线程计算时间。。。这个是估值
N=C*U*(1 + X)
影响线程池大小的因素:CPU数量,内存,使用环境,资源预算,任务占用资源(文件句柄,套接字句柄,数据库连接)
一般来说,如果线程是用来计算的,计算密集型,那么只要设置成CPU支持的线程数量 + 1,1是为了保证万一有一个线程挂了,还有个替补的。多了没什么意义
构建ThreadPoolExecutor
先看一个范例
private final ExecutorService mExecutor = buildDownloadExecutor();
private static ExecutorService buildDownloadExecutor() {
final int maxConcurrent = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_MaxConcurrentDownloadsAllowed);
// Create a bounded thread pool for executing downloads; it creates
// threads as needed (up to maximum) and reclaims them when finished.
final ThreadPoolExecutor executor = new ThreadPoolExecutor(
maxConcurrent, maxConcurrent, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
Log.w(TAG, "Uncaught exception", t);
}
}
};
executor.allowCoreThreadTimeOut(true);
return executor;
}
构造函数是这样的
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
new LinkedBlockingQueue<Runnable>()
) {.......}
corePoolSize: 基本大小
maximumPoolSize:最大大小
keepAliveTIme: 线程存活时间,即线程空闲时间若超过存货时间就回收
unit: 时间单位
最后一项:是线程池中的队列类型,可以说链式阻塞队列,也可以是阻塞队列,在于队列长度的区别
这几项,还是值得多考虑考虑的。
通过调节corePoolSize,maximumPoolSize,keepAliveTime。可以让提高空闲线程的回收效率。------------------回收也是有消耗的。不能设置的太小。
注意事项
1. ThreadPoolExecutor创建后,不会马上启动,要等待有任务提交才会启动,除非调用prestartAllCoreThreads
2. newCachedThreadPool将maximumPoolSize设置成Integer.MAX_VALUE,corePoolSize设成0,keepAliveTIme设置成1minute,这样就可以动态的伸缩线程池大小。而且会被无限扩展。-----------------这里要注意当corePoolSize为0时,什么时候会start thread. 这块没了解,据说如果workQueue不是synchronousQueue,就会导致在corePoolSize跟maximumPoolSize一样为0时,workQueue不为空,有新任务要填满workqueue之后才能start
workQueue管理
workQueue分两种,有界、无解
队列的好处在于 能够降低CPU资源竞争,线程的增加若大于处理速度,那么队列将会不断增加,等待的消耗肯定比竞争的消耗要低
任务排队方式,有三种有界队列,无界队列,没有队列(即直接转交)
newFixedThreadPool和newSingleThreadExecutor默认使用无界版LinkedBlockingQueue(无界队列):这种方式并不是一个可靠的方式,队列的增加会导致资源的不断占用,可能会导致内存消耗殆尽,系统崩溃
所以有界队列更可靠!能避免资源消耗殆尽
直接转交呢适用于非常大的或者无界的线程池,能避免排队。通过SynchronousQueue。任务直接从生产者传递到消费者线程。SynchronousQueue就不是一个队列。直接转交机制的一个实现。
这种直接转交的方式更高效。只有当线程池是无界的,或者可以拒绝任务时才能使用这种方式!
线程间不依赖---------------->有界队列线程池
线程间有依赖---------------->无界队列线程池
队列饱和之-----------饱和策略
线程有四种饱和策略:AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy
AbortPolicy:默认策略。------会抛出未检查的RejectedExecutionException
DiscardPolicy:悄悄抛弃该任务
DiscardOldestPolicy:会抛弃最早提交的,那么这个最早提交的任务、或者优先级最高的任务,就是下一个任务。所以,如果是priorityQueue就不要跟这个策略一起使用。
CallerRunsPolicy:任务退回给调用者,调用者可能会保存这个任务,那么任务在一次一次的累加之后,性能就有一个平缓的下降。而不是一下子就下去了
CallerRunsPolicy调用范例
ThreadPoolExecutor executor = new ThreadPoolExecutor(N_THREADS, N_THREADS, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(CAPACITY));
executor.setRejectedExecutionHandler()new THreadPoolExecutor.CallerRunsPolicy());
Semaphore限制到达率
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)
throws InterruptedException {
semaphore.acquire();
try {
exec.execute(new Runnable(){
public void run() {
try {
command.run();
} finally {
semaphore.release();
}
}
});
} catch(RejectedExecutionException e) {
semaphore.release();
}
}
}
这里的bound=线程数量+队列等待数量
线程的工厂实现
public interface ThreadFactory {
Thread newThread(Runnable run);
}
public class MyThreadFactory implements ThreadFactory {
private final String poolName;
public MyThreadFactory (String poolName) {
this.poolName = poolName;
}
public Thread newThread(Runnable run) {
return new MyThread(run);
}
}
串行跟并行代码浅析
public void Sequentially1(List<Element> eles) {
for (Element e : eles) {
doSomething(e);
}
}
public void Parallel1(Executor exec, List<Element> eles) {
for(Element e : eles) {
exec.executor(new Runnable() {
public void run() {
doSomething(e);
}
});
}
}
利用Executor去做并发执行任务是非常方便的,Executor提供了非常强大方便的框架。
/** Size 1 pool mostly to make systrace output traces on one line. */
private static final Executor SMALL_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1,
1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
也有这么用的。使用还是很方便的。只要了解ThreadPoolExecutor。就行。