传统智慧告诉我们,大容量企业 Java 应用程序应优先使用线程池,而不是生成新的工作线程。 java.util.concurrent
的使用使这变得简单。
但是,确实存在不适合线程池的情况。我目前正在研究的具体示例是使用 InheritableThreadLocal
,它允许将 ThreadLocal
变量“传递”到任何生成的线程。这种机制在使用线程池时会中断,因为工作线程通常不是从请求线程产生的,而是预先存在的。
现在有一些方法可以解决这个问题(可以显式传入线程局部变量),但这并不总是合适或实用的。最简单的解决方案是按需生成新的工作线程,并让 InheritableThreadLocal
完成其工作。
这让我们回到了这个问题——如果我有一个大容量站点,其中用户请求线程每个都产生六个工作线程(即不使用线程池),这会给 JVM 带来问题吗?我们可能会谈论每秒创建几百个新线程,每个线程持续时间不到一秒。现代 JVM 是否对此进行了很好的优化?我记得在 Java 中需要对象池的日子,因为创建对象的成本很高。从那以后,这变得不必要了。我想知道这是否同样适用于线程池。
如果我知道要衡量什么,我会对其进行基准测试,但我担心问题可能比使用分析器衡量的更为微妙。
注意:使用线程局部变量的智慧不是这里的问题,所以请不要建议我不要使用它们。
最佳答案
这是一个微基准测试示例:
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 个线程,即。 e. 线程创建开销为 0.1 毫秒。因此,在这样的机器上,每秒几百个新线程的开销可以忽略不计(通过比较 2 和 100 个线程的列中的数字也可以看出)。
关于Java 线程创建开销,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2117072/