2013-09-05 62 views
5

我寫了兩個自定義類加載器來動態加載代碼。類加載器中的Java死鎖

第一個不負載代碼從一個Jar:

package com.customweb.build.bean.include; 

import java.net.URL; 
import java.net.URLClassLoader; 

import com.customweb.build.process.ILeafClassLoader; 

public class JarClassLoader extends URLClassLoader implements ILeafClassLoader { 

    public JarClassLoader(URL[] urls, ClassLoader parent) { 
     super(urls, parent); 
    } 

    @Override 
    public Class<?> findClassWithoutCycles(String name) throws ClassNotFoundException { 

     Class<?> c = findLoadedClass(name); 
     if (c != null) { 
      return c; 
     } 

     return findClass(name); 
    } 

    @Override 
    protected Class<?> findClass(String qualifiedClassName) throws ClassNotFoundException { 
     synchronized (this.getParent()) { 
      synchronized (this) { 
       return super.findClass(qualifiedClassName); 
      } 
     } 
    } 

    @Override 
    public URL findResourceWithoutCycles(String name) { 
     return super.findResource(name); 
    } 

    @Override 
    public Class<?> loadClass(String name) throws ClassNotFoundException { 
     synchronized (this.getParent()) { 
      synchronized (this) { 
       return super.loadClass(name); 
      } 
     } 
    } 

} 

其他類加載器需要多個類加載器,以允許訪問其他類加載器的類。在第一個初始化期間,我將這個類加載器的一個實例設置爲父類。要打破這個循環,我使用方法'findClassWithoutCycles'。

package com.customweb.build.process; 

import java.net.URL; 
import java.security.SecureClassLoader; 
import java.util.ArrayList; 
import java.util.List; 

public class MultiClassLoader extends SecureClassLoader { 

    private final List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(); 

    public MultiClassLoader(ClassLoader parent) { 
     super(parent); 
    } 

    public void addClassLoader(ClassLoader loader) { 
     this.classLoaders.add(loader); 
    } 

    @Override 
    protected Class<?> findClass(String name) throws ClassNotFoundException { 

     for (ClassLoader loader : classLoaders) { 
      try { 
       if (loader instanceof ILeafClassLoader) { 
        return ((ILeafClassLoader) loader).findClassWithoutCycles(name); 
       } else { 
        return loader.loadClass(name); 
       } 
      } catch (ClassNotFoundException e) { 
       // Ignore it, we try the next class loader. 
      } 
     } 

     throw new ClassNotFoundException(name); 
    } 

    @Override 
    protected URL findResource(String name) { 

     for (ClassLoader loader : classLoaders) { 
      URL url = null; 
      if (loader instanceof ILeafClassLoader) { 
       url = ((ILeafClassLoader) loader).findResourceWithoutCycles(name); 
      } else { 
       url = loader.getResource(name); 
      } 

      if (url != null) { 
       return url; 
      } 
     } 

     return null; 
    } 
} 

但是,當我使用這個類裝載機時,我大部分時間都處於死鎖狀態。我過去在這裏的線程轉儲: http://pastebin.com/6wZKv4Y0

由於一些方法的線程通過$此同步,我嘗試先在的JarClassLoader上MultiClassLoader同步的Java類加載器塊。這應該防止任何死鎖,當獲取鎖的順序相同時。但是,似乎在本地類加載例程中的某處會獲取類加載器的鎖。 我得出這個結論是因爲線程'pool-2-thread-8'被鎖定在對象'0x00000007b0f7f710'上。但在日誌中,我無法看到何時獲取此鎖以及通過哪個線程。

如何找出哪個線程在類加載器上進行同步?

編輯: 我通過在調用MultiClassLoader的loadClass之前同步所有類加載器來解決它。

+0

爲什麼要在父類加載器上同步?我沒有看到任何理由。 – Holger

+0

在類的定義過程中,ClassLoader被用作Lock(同步(this))。當一個調用在MultiClassLoader中完成loadClass()時,這個ClassLoader也被同步。意思是:有些情況下,在JarClassLoader同步之前,父(MultiClassLoader)是同步的。在這種情況下,你會輸入一個死鎖。通過同步你可以避免這種僵局。 –

+0

我看不出如何添加更多同步應該能夠避免死鎖。你的問題證明這個概念是行不通的。不過,我希望我的回答會有所幫助。 – Holger

回答

4

JVM在調用loadClass之前獲取ClassLoader上的鎖。如果類通過一個JarClassLoader引用另一個類加載並且JVM嘗試解析該引用,則會發生這種情況。它將直接進入創建該類的ClassLoader,並將其鎖定並調用loadClass。但是之後你試圖在鎖定JarClassLoader之前鎖定父裝載器。所以兩個鎖的順序不起作用。

但我沒有看到任何兩個鎖的原因,因爲您不訪問任何需要同步的資源。 URLClassLoader的繼承內部狀態由其實現自身維護。

但是,如果您想要爲需要同步的類添加更多狀態,則應該使用不同的機制來鎖定ClassLoader實例。

http://docs.oracle.com/javase/7/docs/technotes/guides/lang/cl-mt.html

如果你有一個自定義的類裝載器與死鎖的風險,與Java SE 7的發佈,您可以按以下規則避免死鎖:

  1. 確保您的自定義類加載器對於併發類加載是多線程安全的。

    a。 決定內部鎖定方案。例如,java.lang.ClassLoader使用基於請求的類名稱的鎖定方案。

    b。單獨刪除類加載器對象鎖上的所有同步

    c。確保關鍵部分對於加載不同類的多個線程是安全的。

  2. 在您的自定義類加載器的靜態初始化器中,調用java.lang.ClassLoader的靜態方法registerAsParallelCapable()。此註冊表明您的自定義類加載器的所有實例都是多線程安全的。

  3. 檢查此自定義類加載器擴展的所有類加載器類是否還在它們的類初始化器中調用registerAsParallelCapable()方法。確保它們是多線程安全的,用於併發類加載。

如果您的自定義類裝載器只覆蓋的findClass(字符串),則不需要進一步的更改。這是建立自定義類加載器的推薦機制。

+0

@Holgar:你的回答非常有幫助。加載類時,我不知道JVM在類加載器上鎖定。但是使用registerAsParallelCapable()並不成功。在調用loadClass()之前,我鎖定了MultiClassLoader中的所有類加載器。這保留了獲取鎖的順序。 –