You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义ClassLoader实现为何引发线程间死锁?

类加载器交叉委托导致的死锁问题分析

问题背景

我有一个继承自URLClassLoader的类加载器LoaderX,实现了以下方法:

override fun loadClass(name: String, resolve: Boolean): Class<*> {
    try {
        return super.loadClass(name, resolve)
    } catch (_: ClassNotFoundException) { }
    return pluginClassLoaderManager.loadClass(name, this)
}

override fun loadClassNoParent(name: String): Class<*> {
    val lock = getClassLoadingLock(name)
    synchronized(lock) {
        return findLoadedClass(name) ?: findClass(name)
    }
}

其中pluginClassLoaderManager是全局类加载器管理器,调用其loadClass方法会将加载请求委托给其他类加载器的loadClassNoParent方法。另外,URLClassLoader的getClassLoadingLock始终返回当前类加载器实例(未启用并行类加载)。

运行程序时出现死锁,线程栈信息如下:

"DefaultDispatcher-worker-3@2727" tid=0x24 nid=NA waiting for monitor entry
  java.lang.Thread.State: BLOCKED
     Blocked by DefaultDispatcher-worker-2@2726
     Waiting for DefaultDispatcher-worker-2@2726 to release lock <0x157e> (x.PluginUrlClassLoader)
     at x.PluginUrlClassLoader.loadClassNoParent(PluginUrlClassLoader.kt:82)
     at x.PluginClassLoaderManager.loadClass(PluginClassLoaderManager.kt:73)
     at x.PluginUrlClassLoader.loadClass(PluginUrlClassLoader.kt:76)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:526)

"DefaultDispatcher-worker-2@2726" tid=0x23 nid=NA waiting for monitor entry
  java.lang.Thread.State: BLOCKED
     Blocked by DefaultDispatcher-worker-3@2727
     Waiting for DefaultDispatcher-worker-3@2727 to release lock <0x157f> (x.PluginUrlClassLoader)
     at x.PluginUrlClassLoader.loadClassNoParent(PluginUrlClassLoader.kt:82)
     at xr.PluginClassLoaderManager.loadClass(PluginClassLoaderManager.kt:73)
     at x.PluginUrlClassLoader.loadClass(PluginUrlClassLoader.kt:76)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:526)

我疑惑的是:从逻辑上看,线程获取锁后应该直接执行findClass并返回,为什么会出现互相等待锁的死锁情况?


死锁原因分析

死锁的核心是两个线程交叉持有对方需要的类加载器锁,形成循环等待,具体触发流程如下:

假设存在两个类加载器实例LoaderA(对应锁<0x157e>)和LoaderB(对应锁<0x157f>):

  1. 线程worker-2先获取了LoaderA的锁,进入LoaderA.loadClassNoParent的同步块,在加载某个类时父类加载失败,于是委托给管理器;管理器将请求转发给LoaderB.loadClassNoParent,此时worker-2需要获取LoaderB的锁,但该锁已经被worker-3持有。
  2. 线程worker-3先获取了LoaderB的锁,进入LoaderB.loadClassNoParent的同步块,加载另一个类时父类加载失败,委托给管理器;管理器将请求转发给LoaderA.loadClassNoParent,此时worker-3需要获取LoaderA的锁,但该锁已经被worker-2持有。

此时双方都持有一个锁,同时等待对方释放另一个锁,完全符合死锁的四个必要条件(互斥、持有并等待、不可剥夺、循环等待),因此陷入死锁。

你误以为死锁发生在findClass执行过程中,但实际上死锁发生在进入另一个类加载器的loadClassNoParent同步块的入口——线程还没来得及执行findClass,就已经卡在等待锁的状态了。


解决建议

  • 避免交叉委托:调整类加载器的委托规则,确保类加载请求是单向依赖(比如LoaderA可以委托LoaderB,但LoaderB不能反向委托LoaderA),或者限制跨加载器委托的类范围。
  • 全局锁控制:对跨加载器的类加载请求,使用一个全局统一的锁来同步,避免交叉等待(但会牺牲部分并发性能)。
  • 循环检测:在管理器的loadClass方法中,检测当前请求的委托链是否存在循环,若存在则抛出异常或跳过委托。

内容的提问来源于stack exchange,提问作者user27909701

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:44:59