Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/315.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java线程创建开销_Java_Performance_Multithreading - Fatal编程技术网

Java线程创建开销

Java线程创建开销,java,performance,multithreading,Java,Performance,Multithreading,传统智慧告诉我们,大容量企业java应用程序应该优先使用线程池,而不是产生新的工作线程。使用java.util.concurrent使这变得简单 但是,确实存在线程池不适合的情况。我目前正在讨论的一个具体示例是使用InheritableThreadLocal,它允许ThreadLocal变量“向下传递”到任何衍生线程。这种机制在使用线程池时会中断,因为工作线程通常不是从请求线程派生的,而是预先存在的 现在有办法解决这个问题(可以显式地传递线程局部变量),但这并不总是合适或实用的。最简单的解决方案

传统智慧告诉我们,大容量企业java应用程序应该优先使用线程池,而不是产生新的工作线程。使用
java.util.concurrent
使这变得简单

但是,确实存在线程池不适合的情况。我目前正在讨论的一个具体示例是使用
InheritableThreadLocal
,它允许
ThreadLocal
变量“向下传递”到任何衍生线程。这种机制在使用线程池时会中断,因为工作线程通常不是从请求线程派生的,而是预先存在的

现在有办法解决这个问题(可以显式地传递线程局部变量),但这并不总是合适或实用的。最简单的解决方案是根据需要生成新的工作线程,并让
InheritableThreadLocal
完成它的工作

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

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


注意:这里的问题不是使用线程局部变量是否明智,因此请不要建议我不使用它们。

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

一个主要因素是分配给每个线程的堆栈内存,您可以使用
-Xssn
JVM参数对其进行调优—您需要使用可以忽略的最低值

这只是一个猜测,但我认为“每秒有几百个新线程”肯定超出了JVM设计的舒适处理能力。我怀疑,一个简单的基准测试会很快暴露出相当不可靠的问题。

  • 对于您的基准测试,您可以使用+a分析器,它可以让您直接了解在这种重载环境中的行为。只需让它运行一个小时,并监控内存、cpu等。如果没有任何故障,cpu也不会过热,就可以了:)

  • 也许您可以获得一个线程池,或者通过添加一些代码来定制(扩展)正在使用的线程池,以便在每次从线程池中获取
    线程时设置相应的
    InheritableThreadLocal
    s。 每个
    线程
    都具有以下包私有属性:

    /* 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()
    结合使用,以获得所需的行为。然而,这有点像广告,而且,我不知道它(带有反射)是否会带来比仅仅创建线程更大的开销


我想知道如果每个用户请求的典型生命周期只有一秒钟那么短,是否有必要在每个用户请求上生成新线程。您是否可以使用某种类型的通知/等待队列,在该队列中生成给定数量的(守护进程)线程,它们都会等待,直到有任务要解决。如果任务队列变长,则会产生额外的线程,但不会以1:1的比例产生。它的性能很可能比生成数百个生命周期如此短的新线程要好。

下面是一个微基准示例:

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();
    }
}
}

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

我想建议,将ThreadLocal包装在访问器方法中可能会解决您在InheritableThreadLocal方面的问题,但您似乎不想听这个。另外,您似乎正在使用InheritableThreadLocal作为带外调用框架,老实说,这似乎是一种代码味道。就线程池而言,主要的好处是控制:您知道您不会突然尝试在一秒钟内启动10000个线程。@kdgregory:首先,Spring的bean作用域使用了讨论中的线程局部变量。这是Spring的工作方式,我无法控制。关于第二点,入站请求线程受到tomcat线程池的限制,因此这种限制是固有的。tomcat线程池如何限制您创建的线程数量?您描述了一个应用程序,其中“用户请求线程[产生]六个工作线程”,我认为您关心的是这些线程。一个bug,一个请求就可以轻松启动10000个线程。但是,关于您需要ThreadLocal的原因:它是有效的,在消息中发布是一件好事,以避免出现愚蠢的评论:-)您描述的是一个线程池,我在问题中已经描述过。如果每个请求线程都充当线程池,我想我只是不明白为什么你不能有一个
私有线程本地,在处理每个工作线程时,您使用
local.set()
/
local.get()
,但我可能误解了您的问题。我发现
new thread()
的含义是一个有趣的概念。在现代JVM中,
newobject()
并不总是分配新内存,而是重用以前的内存
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