并发调用时,setContextClassLoader会大幅减慢

时间:2010-01-04 16:32:17

标签: java multithreading

我的针脚在我的应用程序中指了一个瓶颈,在我看来,它归结为Thread::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的调用不是问题,但是当多个线程正在执行它时,它会大幅减速。

我已经制作了以下测试应用来解决问题:

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~ - 你得到了图片。

我已经尝试过查看Thread的setContextClassLoader实现,看来它所做的只是将一个成员变量设置为传递给它的类加载器。我发现在运行多个线程时没有锁定(或访问需要的共享资源)来解释这种开销。

我在这里缺少什么?

P.S。我正在使用JRE 1.5,但同样的事情发生在1.6。

编辑: @Tom Hawtin - 请参阅我所做的代码更改,以排除您提到的原因。即使提取系统类加载器一次,当线程数> 1时,结果也会变慢。 1。

2 个答案:

答案 0 :(得分:3)

源中唯一非常明显的事情与Thread.setContextClassLoader无关。即使系统类加载器已经初始化,ClassLoader.getSystemClassLoader也会调用锁定initSystemClassLoader的{​​{1}}。

潜在的问题是,读取volatile变量可能会对某些多处理器计算机产生性能影响。

请注意,我们这里仅查看几百个周期。

答案 1 :(得分:2)

最近的JVM使用称为“偏置锁定”的技术,如果只有一个线程访问给定锁,则锁定获取几乎是免费的。第二个线程第一次尝试访问锁定时,对原始访问器的“偏差”被撤销,锁定变为正常/轻量级锁定,需要原子操作来获取(有时需要释放)。

偏置锁和正常锁之间的性能差异可以是一个数量级(比如5个周期对50个周期),这与您的测量结果一致。这里的锁定可能是first reply中提到的锁定问题。如果您有兴趣,可以更详细地描述偏向锁定here

即使忽略偏向锁定,尝试获得相同锁定的两个线程或多个线程的聚合吞吐量通常比单个线程慢得多(因为它们争用包含锁定字的高速缓存行)。