关于类加载的并发控制锁(ClassLoadingLock)

死锁

在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的加载器处理,这样两个加载器都在等待对方处理自己的请求,但有各自又都持有己方的锁,就造成了死锁状态。

BundleA和BundleB相互依赖

 解决

在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锁对象,否则还是使用类加载器本身。

猜你喜欢

转载自blog.csdn.net/huangzhilin2015/article/details/114873607