一、优化线程池线程数量
线程池的大小对系统的性能有一定的影响。过大或者过小的线程数量都无法发挥最优的系统性能,但是线程池大小的确定也不需要做得非常精确,因为只要避免极大和极小两种情况,线程池的大小对系统的性能并不会影响太大。一般来说,确定线程池的大小需要考虑CPU数量、内存大小等因素。在Java Concurrency inPractice
一书中给出了估算线程池大小的公式:
为保持处理器达到期望的使用率,最优的线程池的大小等于:在Java中,可以通过如下代码取得可用的CPU数量。
Runtime.getRuntime().availableProcessors()
二、在线程池中寻找堆栈
下面来看一个简单的案例,首先,我们有一个Runnable接口,它用来计算两个数的商。
public static class DivTask implements Runnable {
int a, b;
public DivTask(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
double re = a / b;
System.out.println(re);
}
}
public static void main(String[] args) {
ThreadPoolExecutor pools = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
for (int i = 0; i < 5; i++) {
pools.submit(new DivTask(100, i));
}
}
上述代码将DivTask提交到线程池,从for循环来看,我们应该会得到5个结果,分别是100除以给定的i后的商。但如果你真的运行程序,那么得到的全部结果是:
只有4个输出,也就是说程序漏算了一组数据,但更不幸的是,程序没有任何日志,也没有任何错误提示,就好像一切正常一样。在这个简单的案例中,只要你稍有经验就能发现,作为除数的i取到了0,这个缺失的值很可能是由于除以0导致的。但在稍复杂的业务场景中,这种错误足可以让你几天萎靡不振。
因此,使用线程池虽然是件好事,但是还是得处处留意这些“坑”。线程池很有可能会“吃”掉程序抛出的异常,导致我们对程序的错误一无所知。
异常堆栈对于程序员的重要性就好像指南针对于航行在茫茫大海上的轮船。没有指南针,轮船只能更艰难地寻找方向,没有异常堆栈,排查问题时,也只能慢慢琢磨了。我的一个领导曾经说过:“最鄙视那些出错不打印异常堆栈的行为!”我相信,任何一个得益于异常堆栈而快速定位问题的程序员,一定都对这句话深有体会。这里我们将和大家讨论向线程池讨回异常堆栈的方法。
一种最简单的方法就是放弃submit()方法,改用execute()方法。将上述的任务提交代码改成:
pools.execute(new DivTask(100,i));
或者使用下面的方法改造你的submit()方法:
Future re = pools.submit(new DivTask(100,i));
re.get();
上面两种方法都可以得到部分堆栈信息,如下所示:
注意了,我这里说的是部分。这是因为从这两个异常堆栈中我们只能知道异常是在哪里抛出的(这里是DivTask的第11行)。但是我们还希望得到另外一个更重要的信息,那就是这个任务到底是在哪里提交的?而任务的具体提交位置已经被线程池完全淹没了。顺着堆栈,我们最多只能找到线程池中的调度流程,而这对于我们几乎是没有价值的。
既然这样,我们只能自己动手,丰衣足食啦!为了今后少加几天班,非常有必要将堆栈的信息彻底挖出来!扩展我们的ThreadPoolExecutor
线程池,让它在调度任务之前,先保存一下提交任务线程的堆栈信息:
package ch3.s2;
import java.util.concurrent.*;
public class TraceThreadPoolExecutor extends ThreadPoolExecutor {
public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
public void execute(Runnable task) {
super.execute(wrap(task, clientTrace(), Thread.currentThread().getName()));
}
@Override
public Future<?> submit(Runnable task) {
return super.submit(wrap(task, clientTrace(), Thread.currentThread().getName()));
}
public Exception clientTrace() {
return new Exception("Client stack trace");
}
//第二个参数是异常,保存提交任务的线程的堆栈信息
//该方法将我们传入的 Runnable 任务进行一层包装,使之能处理异常信息,当任务发生异常时,异常会被打印
public Runnable wrap(final Runnable task, final Exception clientStack, String clientThreadName) {
return new Runnable() {
@Override
public void run() {
try {
task.run();
} catch (Exception e) {
clientStack.printStackTrace();
throw e;
}
}
};
}
public static class DivTask implements Runnable {
int a, b;
public DivTask(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
double re = a / b;
System.out.println(re);
}
}
public static void main(String[] args) {
ThreadPoolExecutor pools = new TraceThreadPoolExecutor(0, Integer.MAX_VALUE, 0L, TimeUnit.SECONDS, new
SynchronousQueue<Runnable>());
for (int i = 0; i < 5; i++) {
pools.execute(new DivTask(100, i));
}
}
}