Java线程创建开销

时间:2010-01-22 12:11:51

标签: java performance multithreading

传统智慧告诉我们,大批量企业Java应用程序应优先使用线程池来生成新的工作线程。使用java.util.concurrent可以直截了当。

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

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

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

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

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

4 个答案:

答案 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比率。它最有可能表现更好,然后产生数百个生命周期如此短的新线程。