死锁
在JDK1.7以前,java.lang.ClassLoader的一些核心方法是被synchronized修饰的,比如loadClass,以下摘自JDK6下java.lang.ClassLoader的部分方法:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {...}
private synchronized Class loadClassInternal(String name) throws ClassNotFoundException {...}
private synchronized void checkCerts(String name, CodeSource cs) {...}
private static synchronized void initSystemClassLoader() {...}
在传统的双亲委派模型下,使用synchronized来做并发控制是没有问题的,但是如果出现类加载相互依赖的情况,就容易出现死锁。一个典型的例子就是OSGI的多个模块相互依赖对方发布的package,当一个模块需要加载依赖的package时,需要委派给发布该package的模块类加载器加载。关于OSGI简介可以参考:《深入理解Java虚拟机》读书笔记(八)--类加载及执行子系统案例(Tomcat类加载、OSGI、动态代理)。如果出现以下情况:
-
BundleA:发布packageA,依赖packageB
-
BundleB:发布packageB,依赖packageA
由于方法被synchronized修饰,当BundleA加载PackageB时,要先锁定BundleA类加载器的实例对象,然后将请求委派给BundleB的类加载器处理,但如果这时BundleB也正好想要加载packageA的类,就需要先锁定BundleB类加载器再去请求BundleA的加载器处理,这样两个加载器都在等待对方处理自己的请求,但有各自又都持有己方的锁,就造成了死锁状态。
解决
在JDK1.7之后对这个问题做出了优化,可以以手动注册的方式将类加载标识为具备并行能力,之后在加载类的时候弃用了方法级别synchronized的方式,改为了为每个要加载的class文件(全路径名)都对应一个Object对象锁。加锁的时候如果加载器具备并行能力,那么就不再对类加载器进行加锁,而是找到加载类文件对应的Object锁进行加锁操作。参考:http://openjdk.java.net/projects/jdk7/features/#f352
比如这样注册:
static {
ClassLoader.registerAsParallelCapable();
}
registerAsParallelCapable是ClassLoader的静态方法,其中会调用ParallelLoaders.register方法,ParallelLoaders是ClassLoader的一个静态内部类,其中包含一个Set类型的静态属性,上面的注册操作会把类加载器添加到其中:
//Set类型,存放具备并行能力的类加载器
private static final Set<Class<? extends ClassLoader>> loaderTypes =
Collections.newSetFromMap(
new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
//将类加载器注册到loaderTypes中
static boolean register(Class<? extends ClassLoader> c) {
synchronized (loaderTypes) {
if (loaderTypes.contains(c.getSuperclass())) {
loaderTypes.add(c);
return true;
} else {
return false;
}
}
}
当类加载器实例化的时候,父类ClassLoader(所有自定义类加载器都会直接或间接继承自ClassLoader)的构造器中会判断当前加载器是否具备并行能力(是否在loaderTypes中),如果具备,那么初始化一个CurrentHashMap类型的非静态属性parallelLockMap,用于存放锁对象,key是需要加载的字节码文件名,value是Object锁对象:
private final ConcurrentHashMap<String, Object> parallelLockMap;
private ClassLoader(Void unused, ClassLoader parent) {
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
}
}
在ClassLoade需要加锁的方法定义中,都移除了synchronized修饰,改为在方法中获取锁再synchronized,以loadClass为例:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {...}
}
着重看看getClassLoadingLock方法:
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
可以看到,如果类加载器具备并行能力,创建了parallelLockMap,就会为每一个className都创建一个Object锁对象,否则还是使用类加载器本身。