Java 并发调用setContextClassLoader时速度会急剧降低
在我的应用程序中,我已经指出了一个瓶颈,在我看来,它归结为一个调用 基本上,由于第三方库的问题,我不得不处理线程的上下文类加载器(请参阅以了解原因) 据我所知,我选择的解决方案是常见的,其工作原理如下:Java 并发调用setContextClassLoader时速度会急剧降低,java,multithreading,Java,Multithreading,在我的应用程序中,我已经指出了一个瓶颈,在我看来,它归结为一个调用 基本上,由于第三方库的问题,我不得不处理线程的上下文类加载器(请参阅以了解原因) 据我所知,我选择的解决方案是常见的,其工作原理如下: Thread thread = Thread.currentThread(); ClassLoader old = thread.getContextClassLoader(); thread.setContextClassLoader(newClassLoader); try { .
Thread thread = Thread.currentThread();
ClassLoader old = thread.getContextClassLoader();
thread.setContextClassLoader(newClassLoader);
try {
... // problematic code that uses the thread context class loader
} finally {
thread.setContextClassLoader(old);
}
事实证明,当只有一个线程在运行时,调用setContextClassLoader不是问题,但是当多个线程在运行时,它会急剧减慢
我制作了以下测试应用程序来隔离问题:
ArrayList<Thread> threads = new ArrayList<Thread>();
int thread_count = 1;
long start = System.currentTimeMillis();
for (int i = 0; i < thread_count; i++) {
Thread thread = new Thread(new MyRunnable(100000000));
thread.start();
threads.add(thread);
}
for (Thread thread : threads) {
thread.join();
}
long total = System.currentTimeMillis() - start;
double seconds = (double)total / 1000;
System.out.println("time in seconds: " + seconds);
ArrayList threads=new ArrayList();
int线程计数=1;
长启动=System.currentTimeMillis();
对于(int i=0;i
这是MyRunnable类:
public class MyRunnable implements Runnable {
int _iterations;
public MyRunnable(int iterations) {
_iterations = iterations;
}
public void run() {
final Thread curr = Thread.currentThread();
final ClassLoader loader = ClassLoader.getSystemClassLoader();
for (int i = 0; i < _iterations; i++) {
curr.setContextClassLoader(loader);
}
}
}
公共类MyRunnable实现Runnable{
整数迭代;
公共MyRunnable(整数迭代){
_迭代=迭代;
}
公开募捐{
最终线程电流=Thread.currentThread();
final ClassLoader=ClassLoader.getSystemClassLoader();
对于(int i=0;i<\u迭代;i++){
当前setContextClassLoader(加载器);
}
}
}
基本上,它打开两个线程,并在循环中将当前线程上下文类装入器设置为系统类装入器
在我的机器上更改代码后更新结果:当线程计数
为1时,它将在半秒钟内完成。2条线取1.5~,3条线取2.7~,4条线取4~-好了,你就知道了
我试着查看了setContextClassLoader的线程实现,似乎它所做的只是为传递给它的类加载器设置一个成员变量。我发现,在使用多个线程运行时,没有任何锁定(或对共享资源的访问)来解释这种开销
我错过了什么
另外,我使用的是JRE 1.5,但同样的事情也发生在1.6中
编辑:@Tom Hawtin-查看我对代码所做的更改,以排除您提到的原因。即使只提取一次系统类加载器,当线程计数大于1时,结果也会变慢。源代码中唯一明显的事情与
线程无关。setContextClassLoader
ClassLoader.getSystemClassLoader
调用锁定ClassLoader.class
的initSystemClassLoader
,即使系统类装入器已经初始化
一个潜在的问题是,易变变量的读取可能会对某些多处理器机器的性能产生影响
请注意,这里我们只看几百个周期。最近的JVM使用了一种称为“偏置锁定”的技术,如果只有一个线程访问给定的锁,那么锁获取几乎是免费的。当第二个线程第一次尝试访问锁时,对原始访问器的“偏向”被撤销,锁变成一个普通的/轻量级的锁,需要一个原子操作来获取(有时需要一个原子操作来释放) 偏置锁和正常锁之间的性能差异可能是一个数量级(比如5个周期vs 50个周期),这与您的测量结果一致。这里讨论的锁可能就是您的问题中提到的锁。如果您感兴趣,将更详细地描述偏置锁定
即使忽略有偏锁定,两个或多个试图获得相同锁的线程在聚合吞吐量上通常也会比单个线程慢得多(因为它们争用包含锁字的缓存线)。只有几百个周期,但大部分用户代码本身只有几个周期,因此,这很容易导致他所看到的经济放缓。此外,锁有效地排除了并行性。是的,setContextClassLoader()调用会增加时间,但在真正的应用程序中,您不会在紧循环中调用它一千万次。使用量只占应用程序总使用量的很小一部分。不用担心。不要调用getSystemClassLoader,而是创建一个空的URLClassLoader-您将看到相同的结果。添加
final ClassLoader system=ClassLoader.getSystemClassLoader()代码>外部循环(并使用它)基本上为我解决了问题。在所有情况下都能显著提高性能。请参阅更新的代码-即使只有一次对getSystemClassLoader()的调用,结果也不会改变。在我的机器上(双核Athlon、32位Vista、Sun JRE 6u17),1、2、3、4或5个线程的运行时间大约为0.6秒,尽管这个微基准的工作量与线程的数量成正比。基准测试失败了。现在每个操作10纳秒是很小的!!你在抱怨什么?时间上的差异可能是由于热点编译器(您没有提到您使用的是什么JAVA实现)在单独的线程中编译,然后在单独的线程中进行修补。尝试将main
的主体放在不同的位置运行五次。查看更新的代码-即使只有一次对getSystemClassLoader()的调用,结果也会保持不变。当然,但是为什么不希望结果变慢呢?正如Tom指出的,所做的工作与线程的数量成正比——4个线程的循环次数是4倍。你有多少个CPU?你有阅读吗?您还可能会遇到抖动,因为测试直到所有CPU完成才结束,尽管大多数线程可能会更早完成。