传统智慧告诉我们,大批量企业Java应用程序应优先使用线程池来生成新的工作线程。使用java.util.concurrent
可以直截了当。
然而,确实存在线程池不适合的情况。我目前正在努力解决的具体示例是使用InheritableThreadLocal
,它允许ThreadLocal
变量“传递”到任何生成的线程。使用线程池时,此机制会中断,因为工作线程通常不是从请求线程生成的,而是预先存在的。
现在可以解决这个问题(线程本地可以显式传入),但这并不总是合适或实用。最简单的解决方案是按需生成新的工作线程,让InheritableThreadLocal
完成它的工作。
这让我们回到了这个问题 - 如果我有一个高容量站点,用户请求线程每个产生六个工作线程(即不使用线程池),这是否会给JVM带来问题?我们可能会谈论每秒创建几百个新线程,每个线程持续不到一秒钟。现代JVM是否能很好地优化这一点?我记得在Java中需要对象池的日子,因为对象创建很昂贵。从此变得不必要了。我想知道是否同样适用于线程池。
如果我知道要测量什么,我会对它进行基准测试,但我担心这些问题可能会比使用剖析器测量的更微妙。
注意:使用线程本地的智慧不是问题所在,所以请不要建议我不要使用它们。
答案 0 :(得分:37)
以下是微基准测试的示例:
public class ThreadSpawningPerformanceTest {
static long test(final int threadCount, final int workAmountPerThread) throws InterruptedException {
Thread[] tt = new Thread[threadCount];
final int[] aa = new int[tt.length];
System.out.print("Creating "+tt.length+" Thread objects... ");
long t0 = System.nanoTime(), t00 = t0;
for (int i = 0; i < tt.length; i++) {
final int j = i;
tt[i] = new Thread() {
public void run() {
int k = j;
for (int l = 0; l < workAmountPerThread; l++) {
k += k*k+l;
}
aa[j] = k;
}
};
}
System.out.println(" Done in "+(System.nanoTime()-t0)*1E-6+" ms.");
System.out.print("Starting "+tt.length+" threads with "+workAmountPerThread+" steps of work per thread... ");
t0 = System.nanoTime();
for (int i = 0; i < tt.length; i++) {
tt[i].start();
}
System.out.println(" Done in "+(System.nanoTime()-t0)*1E-6+" ms.");
System.out.print("Joining "+tt.length+" threads... ");
t0 = System.nanoTime();
for (int i = 0; i < tt.length; i++) {
tt[i].join();
}
System.out.println(" Done in "+(System.nanoTime()-t0)*1E-6+" ms.");
long totalTime = System.nanoTime()-t00;
int checkSum = 0; //display checksum in order to give the JVM no chance to optimize out the contents of the run() method and possibly even thread creation
for (int a : aa) {
checkSum += a;
}
System.out.println("Checksum: "+checkSum);
System.out.println("Total time: "+totalTime*1E-6+" ms");
System.out.println();
return totalTime;
}
public static void main(String[] kr) throws InterruptedException {
int workAmount = 100000000;
int[] threadCount = new int[]{1, 2, 10, 100, 1000, 10000, 100000};
int trialCount = 2;
long[][] time = new long[threadCount.length][trialCount];
for (int j = 0; j < trialCount; j++) {
for (int i = 0; i < threadCount.length; i++) {
time[i][j] = test(threadCount[i], workAmount/threadCount[i]);
}
}
System.out.print("Number of threads ");
for (long t : threadCount) {
System.out.print("\t"+t);
}
System.out.println();
for (int j = 0; j < trialCount; j++) {
System.out.print((j+1)+". trial time (ms)");
for (int i = 0; i < threadCount.length; i++) {
System.out.print("\t"+Math.round(time[i][j]*1E-6));
}
System.out.println();
}
}
}
在Intel Core2 Duo E6400 @ 2.13 GHz上使用32位Sun的Java 1.6.0_21客户端VM的64位Windows 7上的结果如下:
Number of threads 1 2 10 100 1000 10000 100000
1. trial time (ms) 346 181 179 191 286 1229 11308
2. trial time (ms) 346 181 187 189 281 1224 10651
结论:由于我的计算机有两个核心,因此两个线程的工作速度几乎是一个线程的两倍。我的电脑每秒可以产生近10000个线程,i。即线程创建开销为0.1毫秒。因此,在这样的机器上,每秒几百个新线程构成可忽略的开销(通过比较2和100个线程的列中的数字也可以看出)。
答案 1 :(得分:9)
首先,这当然很大程度上取决于您使用的JVM。操作系统也将发挥重要作用。假设Sun JVM(嗯,我们还称它为吗?):
一个主要因素是分配给每个线程的堆栈内存,您可以使用-Xssn
JVM参数进行调整 - 您将希望使用可以获得的最低值。
这只是猜测,但我认为“每秒几百个新线程”绝对超出了JVM设计的舒适性。我怀疑一个简单的基准测试会很快揭示出相当不确定的问题。
答案 2 :(得分:1)
对于您的基准测试,您可以使用JMeter +一个分析器,它可以让您直接了解这种负载较重的环境中的行为。让它运行一个小时并监视内存,CPU等。如果没有任何中断并且cpu(s)没有过热,那就没问题了:)
也许您可以通过添加一些代码来获取线程池,或者自定义(扩展)您正在使用的线程池,以便每次InheritableThreadLocal
时都设置相应的Thread
从线程池中获取。
每个Thread
都有这些包私有属性:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
您可以将这些(与反射一起使用)与Thread.currentThread()
结合使用,以获得所需的行为。然而,这是一个广告,而且,我无法判断它(与反射)是否会产生比创建线程更大的开销。
答案 3 :(得分:0)
我想知道是否有必要在每个用户请求上生成新线程,如果它们的典型生命周期短至一秒钟。你可以使用某种Notify / Wait队列来产生给定数量的(守护进程)线程,它们都会等到有任务要解决。如果任务队列变长,则会产生其他线程,但不会产生1-1比率。它最有可能表现更好,然后产生数百个生命周期如此短的新线程。