我在我的應用程序中有針腳指向瓶頸,在我看來,它歸結爲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