线程池(二)

性能问题

饥饿死锁

如果线程池中的任务依赖于之后提交的子任务,当线程池不够大的时候,很容易造成饥饿死锁。所以最好在线程池中加入的是同类型的独立任务。

运行时间较长的任务

如果线程运行时间较长也会影响任务的相应性,同样造成不好的体验,所以api有很多方法都带有一个限时版本。

线程池大小

线程池的大小需要分析计算环境,资源预算和任务特性。

一般来说在知道了系统中有多少个cpu和内存的基础下,任务类型是最为重要的。

  • 对于计算密集型的任务线程池大小为cpu数+1,以实现尽可能的满载利用率
  • 对于i/o密集型,由于线程不会一直执行,所以规模更大。这里给出一个《Java并发变成实战》这本书提出的一个公式

最佳线程数目 = (线程等待时间/线程CPU时间之比 + 1) CPU数目cpu利用率

即等待时间越长,需要更多的线程。
当我们不需要一个那么精准的线程数目时,也可以用这个公式

最佳线程数目 = 2N+1(N为CPU数目)

是否使用线程池就一定比使用单线程高效呢?

答案是否定的,比如Redis就是单线程的,但它却非常高效,基本操作都能达到十万量级/s。从线程这个角度来看,部分原因在于:

  • 多线程带来线程上下文切换开销,单线程就没有这种开销
  • 当然“Redis很快”更本质的原因在于:Redis基本都是内存操作,这种情况下单线程可以很高效地利用CPU。而多线程适用场景一般是:存在相当比例的IO和网络操作。

扩展线程池

ThreadPoolExecutor提供了几个可以在子类化中该学的方法:

  • beforeExecute
  • afterExecute
  • terminated

如果beforeExecute抛出一个RuntimeException,那么任务将不被执行,并且afterExecute也不会被调用。

但是无论人物从run中正常返回还是抛出一个异常返回,afterExecute都会被调用,如果任务在完成后带有一个Error,那么久不会调用。

所有任务都已经完成并且所有工作者线程也已经关闭后,terminated会被调用。

给出一个demo,他通过这一些列方法来统计任务执行并添加日志。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class TimingThreadPool extends ThreadPoolExecutor {

public TimingThreadPool() {
super(1, 1, 0L, TimeUnit.SECONDS, null);
}

private final ThreadLocal<Long> startTime = new ThreadLocal<Long>();
private final Logger log = Logger.getLogger("TimingThreadPool");
private final AtomicLong numTasks = new AtomicLong();
private final AtomicLong totalTime = new AtomicLong();

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());
}

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);
}
}

protected void terminated() {
try {
log.info(String.format("Terminated: avg time=%dns",
totalTime.get() / numTasks.get()));
} finally {
super.terminated();
}
}
}

引用

http://ifeve.com/how-to-calculate-threadpool-size/
《Java并发编程实战》