传统观点告诉我们,大批量企业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个线程,即线程创建开销为0.1毫秒.因此,在这样的机器上,每秒几百个新线程构成可忽略的开销(通过比较2和100个线程的列中的数字也可以看出).
首先,这当然很大程度上取决于您使用的JVM.操作系统也将发挥重要作用.假设Sun JVM(嗯,我们还称它为吗?):
一个主要因素是分配给每个线程的堆栈内存,您可以使用-Xssn
JVM参数进行调整- 您将希望使用可以获得的最低值.
这只是一个猜测,但我认为"每秒几百个新线程"绝对超出了JVM设计的舒适性.我怀疑一个简单的基准测试会很快揭示出相当不容置妥的问题.