最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - Why does my custom ClassLoader implementation cause deadlock between threads? - Stack Overflow

programmeradmin2浏览0评论

I have a class loader LoaderX that inherits from URLClassLoader. This class loader implements the following methods:

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)
    }
}

Here, pluginClassLoaderManager is a class loader manager that manages all the class loaders in the program. Calling its loadClass method delegates the loading operation to the loadClassNoParent method of other class loaders.

The implementation of getClassLoadingLock in URLClassLoader always returns this (I have not enabled parallel class loading).

In theory, this should work fine, but when I run the program, I find that this logic can potentially lead to a deadlock:

"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)

The two threads are waiting for each other to release locks, but I can't figure out under what circumstances this conflict would occur. Logically, once a thread acquires a lock, it should execute its own findClass logic and then return a Class (or throw an exception). Why would two threads end up waiting for each other's locks?

I have a class loader LoaderX that inherits from URLClassLoader. This class loader implements the following methods:

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)
    }
}

Here, pluginClassLoaderManager is a class loader manager that manages all the class loaders in the program. Calling its loadClass method delegates the loading operation to the loadClassNoParent method of other class loaders.

The implementation of getClassLoadingLock in URLClassLoader always returns this (I have not enabled parallel class loading).

In theory, this should work fine, but when I run the program, I find that this logic can potentially lead to a deadlock:

"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)

The two threads are waiting for each other to release locks, but I can't figure out under what circumstances this conflict would occur. Logically, once a thread acquires a lock, it should execute its own findClass logic and then return a Class (or throw an exception). Why would two threads end up waiting for each other's locks?

Share Improve this question edited yesterday Mark Rotteveel 109k229 gold badges156 silver badges223 bronze badges asked yesterday user27909701user27909701 112 bronze badges 2
  • You should probably lock on getClassLoadingLock(name) for the entire duration of your current loadClass implementation. – Mark Rotteveel Commented yesterday
  • Add a minimal reproducible example in the question. – aled Commented yesterday
Add a comment  | 

1 Answer 1

Reset to default 0

Instead of relying on instance-level locks (getClassLoadingLock), consider using a centralized locking mechanism for all your custom class loaders. You can implement a global lock map based on class names being loaded

private val lockRegistry = ConcurrentHashMap<String, Any>()

private fun getGlobalLoadingLock(name: String): Any {
    return lockRegistryputeIfAbsent(name) { Any() }
}

Then, whenever loadClassNoParent or pluginClassLoaderManager.loadClass is invoked, synchronize on this global lock for the specific class name

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

This ensures that loadClassNoParent for the same class name is never called concurrently across different threads.

发布评论

评论列表(0)

  1. 暂无评论