Java线程创建开销

ska*_*man 45 java performance multithreading

传统观点告诉我们,大批量企业Java应用程序应优先使用线程池来生成新的工作线程.使用java.util.concurrent使这简单明了.

但是,确实存在线程池不适合的情况.我目前正在努力的具体示例是使用InheritableThreadLocal,它允许ThreadLocal变量"传递"到任何生成的线程.使用线程池时,此机制会中断,因为工作线程通常不是从请求线程生成的,而是预先存在的.

现在有办法解决这个问题(线程本地可以显式传入),但这并不总是合适或实用.最简单的解决方案是按需生成新的工作线程,然后让它InheritableThreadLocal完成它的工作.

这让我们回到刚才的问题 - 如果我有一个高容量的网站,在这里用户请求的线程产卵关每个半打工作线程(即不使用线程池),这是怎么回事给JVM的一个问题?我们可能会谈论每秒创建几百个新线程,每个线程持续不到一秒钟.现代JVM是否能很好地优化这一点?我记得在Java中需要对象池的日子,因为对象创建很昂贵.从此变得不必要了.我想知道是否同样适用于线程池.

如果我知道要测量什么,我会对它进行基准测试,但我担心的是问题可能比用剖析器测量的更微妙.

注意:使用线程本地的智慧不是问题所在,所以请不要建议我不要使用它们.

Jaa*_*aan 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();
    }
}
}
Run Code Online (Sandbox Code Playgroud)

在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
Run Code Online (Sandbox Code Playgroud)

结论:由于我的计算机有两个核心,因此两个线程的工作速度几乎是一个线程的两倍.我的计算机每秒可以生成近10000个线程,即线程创建开销为0.1毫秒.因此,在这样的机器上,每秒几百个新线程构成可忽略的开销(通过比较2和100个线程的列中的数字也可以看出).


Mic*_*rdt 9

首先,这当然很大程度上取决于您使用的JVM.操作系统也将发挥重要作用.假设Sun JVM(嗯,我们还称它为吗?):

一个主要因素是分配给每个线程的堆栈内存,您可以使用-XssnJVM参数进行调整- 您将希望使用可以获得的最低值.

这只是一个猜测,但我认为"每秒几百个新线程"绝对超出了JVM设计的舒适性.我怀疑一个简单的基准测试会很快揭示出相当不容置妥的问题.

  • 我发现`new Thread()`意味着什么是有趣的概念.在现代JVM中,`new Object()`并不总是分配新的内存,它重用以前的垃圾收集对象.我想知道是否有任何理由为什么JVM不能拥有一个隐藏的,可重用的线程内部池,因此`new Thread()`不一定会创建一个新的内核线程.您将获得有效的线程池,而无需使用API​​. (2认同)
  • 如果是这样的话,应该在一些JSR中找到它.可能是133 http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf (2认同)