我在我的应用程序中有针脚指向瓶颈,在我看来,它归结为Thread::setContextClassLoader的呼叫。setContextClassLoader在同时调用时速度显着减慢
基本上我被迫更动线程的上下文类加载由于与第三方库(见this question明白为什么)问题。
我拿起了我的知识常见的一种,它的工作原理是这样的解决方案:
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不是一个问题,当只有1个线程运行,但当多个线程正在执行时,它会急剧减速。
我做了以下测试程序来隔离问题:
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);
这是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);
}
}
}
基本上它打开了几个线程,并设置当前线程上下文类加载器循环到系统类加载器。
在我的机器上更改了代码后的结果:当thread_count
为1时,它在半秒钟内完成。 2个线程占用1.5〜3个线程2.7〜4个线程4〜 - 你得到的图片。
我试过寻找线程的setContextClassLoader的实现,它似乎只是设置一个成员变量传递给它的类加载器。我发现在使用多个线程运行时没有锁定(或访问需要的共享资源)来解释这种开销。
我在这里错过了什么?
P.S.我正在使用JRE 1.5,但在1.6中发生了相同的事情。
编辑: @Tom Hawtin - 查看我所做的代码更改以排除您提到的原因。即使系统类加载器被提取一次,当线程数大于1时结果也会变慢。
只有几百个周期,但大部分用户代码本身只有几个周期,所以这很容易导致他看到的放缓。此外,锁有效地排除了并行性。是的,setContextClassLoader()调用增加了时间,但在真实应用程序中,您不会在紧密循环中调用它1000万次。这个用法将是你应用程序总使用量的微观部分。不要担心。 – 2010-01-04 17:51:13
而不是调用getSystemClassLoader,创建一个空的URLClassLoader - 你会看到相同的结果。 – 2010-01-04 18:08:59
在循环外部(并使用它)添加'final ClassLoader system = ClassLoader.getSystemClassLoader();主要为我解决了这个问题。在所有情况下也大大提高性能。 – 2010-01-04 18:46:23