代码之家  ›  专栏  ›  技术社区  ›  Idan K

当同时调用时,setContextClassLoader会大大降低速度

  •  3
  • Idan K  · 技术社区  · 15 年前

    我的应用程序中有一个瓶颈,在我看来,它归结为 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);
    

    这是我无法命名的类:

    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实现中查找,它所做的一切似乎都是为传递给它的类加载器设置一个成员变量。我发现在使用多个线程运行时,没有任何锁(或对共享资源的访问)来解释这种开销。

    我这里缺什么?

    另外,我使用的是JRE1.5,但在1.6中也发生了同样的事情。

    编辑: @汤姆·霍丁-看看我为排除你提到的原因而做的代码修改。即使系统类加载器被提取一次,当线程计数为>1时,结果也会变慢。

    2 回复  |  直到 12 年前
        1
  •  3
  •   Tom Hawtin - tackline    15 年前

    源代码中唯一真正明显的事情与 Thread.setContextClassLoader . ClassLoader.getSystemClassLoader 电话 initSystemClassLoader 那锁 ClassLoader.class 即使系统类加载器已经初始化。

    一个潜在的问题是易变变量的读取可能会对某些多处理器机器产生性能影响。

    注意,这里我们只看几百个循环。

        2
  •  2
  •   Community Michael Schmitz    7 年前

    最近的JVM使用了一种称为“偏向锁”的技术,如果只有一个线程访问给定的锁,那么锁获取几乎是免费的。当第二个线程第一次尝试访问锁时,对原始访问器的“偏向”将被撤销,并且锁将成为一个普通/轻量级锁,需要原子操作来获取(有时需要释放一个)。

    偏向锁和普通锁之间的性能差异可以是一个数量级(比如5个周期对50个周期),这与您的度量一致。这里讨论的锁可能是 first reply 回答你的问题。更详细地描述了偏向锁 here 如果你感兴趣的话。

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