Java线程创建开销
传统智慧告诉我们,大容量企业java应用程序应该优先使用线程池,而不是产生新的工作线程。使用Java线程创建开销,java,performance,multithreading,Java,Performance,Multithreading,传统智慧告诉我们,大容量企业java应用程序应该优先使用线程池,而不是产生新的工作线程。使用java.util.concurrent使这变得简单 但是,确实存在线程池不适合的情况。我目前正在讨论的一个具体示例是使用InheritableThreadLocal,它允许ThreadLocal变量“向下传递”到任何衍生线程。这种机制在使用线程池时会中断,因为工作线程通常不是从请求线程派生的,而是预先存在的 现在有办法解决这个问题(可以显式地传递线程局部变量),但这并不总是合适或实用的。最简单的解决方案
java.util.concurrent
使这变得简单
但是,确实存在线程池不适合的情况。我目前正在讨论的一个具体示例是使用InheritableThreadLocal
,它允许ThreadLocal
变量“向下传递”到任何衍生线程。这种机制在使用线程池时会中断,因为工作线程通常不是从请求线程派生的,而是预先存在的
现在有办法解决这个问题(可以显式地传递线程局部变量),但这并不总是合适或实用的。最简单的解决方案是根据需要生成新的工作线程,并让InheritableThreadLocal
完成它的工作
这让我们回到问题上来——如果我有一个高容量的站点,其中每个用户请求线程都会产生六个工作线程(即不使用线程池),这会给JVM带来问题吗?我们可能正在谈论每秒创建几百个新线程,每个线程持续不到一秒钟。现代JVM能很好地优化这一点吗?我记得在Java中对象池是可取的,因为对象创建是昂贵的。这已经变得不必要了。我想知道这是否也适用于线程池
如果我知道该测量什么,我会对其进行基准测试,但我担心的是,问题可能比用分析器测量的更微妙
注意:这里的问题不是使用线程局部变量是否明智,因此请不要建议我不使用它们。首先,这当然在很大程度上取决于您使用的JVM。操作系统也将发挥重要作用。假设Sun JVM(嗯,我们还是这么称呼它吗?): 一个主要因素是分配给每个线程的堆栈内存,您可以使用
-Xssn
JVM参数对其进行调优—您需要使用可以忽略的最低值
这只是一个猜测,但我认为“每秒有几百个新线程”肯定超出了JVM设计的舒适处理能力。我怀疑,一个简单的基准测试会很快暴露出相当不可靠的问题。- 对于您的基准测试,您可以使用+a分析器,它可以让您直接了解在这种重载环境中的行为。只需让它运行一个小时,并监控内存、cpu等。如果没有任何故障,cpu也不会过热,就可以了:)
- 也许您可以获得一个线程池,或者通过添加一些代码来定制(扩展)正在使用的线程池,以便在每次从线程池中获取
线程时设置相应的
s。 每个InheritableThreadLocal
都具有以下包私有属性:线程
您可以将这些(以及反射)与/* 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()
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