Bloqueo de control de simultaneidad en la carga de clases (ClassLoadingLock)

Punto muerto

Antes de JDK1.7, algunos métodos principales de java.lang.ClassLoader se modificaban sincronizados, como loadClass. A continuación, se muestra un extracto de algunos métodos de java.lang.ClassLoader en JDK6:

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() {...}

Bajo el modelo de delegación parental tradicional, no hay ningún problema al usar sincronizado para realizar el control de concurrencia, pero si existe una situación en la que la carga de clases depende entre sí, es propenso a un punto muerto. Un ejemplo típico es que varios módulos de OSGI dependen del paquete publicado entre sí. Cuando un módulo necesita cargar un paquete dependiente, debe delegarse en el cargador de clases de módulo que liberó el paquete para cargar. Para obtener una introducción a OSGI, consulte: "Comprensión en profundidad de la máquina virtual Java" notas de lectura (ocho) casos de subsistemas de carga y ejecución de clases (carga de clases Tomcat, OSGI, proxy dinámico) . Si ocurren las siguientes situaciones:

  • BundleA: paquete de lanzamientoA, depende del paqueteB

  • BundleB: lanzamiento del paqueteB, depende del paqueteA

Debido a que el método se modifica sincronizado, cuando BundleA carga PackageB, primero debe bloquear el objeto de instancia del cargador de clases BundleA y luego delegar la solicitud al cargador de clases de BundleB. Pero si BundleB también solo quiere cargar la clase de packageA, entonces es necesario bloquear el cargador de clases BundleB antes de solicitar que el cargador BundleA procese, de modo que ambos cargadores estén esperando que el otro procese sus propias solicitudes, pero cada uno tiene su propio bloqueo, lo que provoca un estado de punto muerto.

BundleA y BundleB dependen el uno del otro

 resolver

Después de JDK1.7, este problema se ha optimizado. La carga de clases se puede marcar como con capacidad paralela mediante el registro manual. Más tarde, cuando se cargan clases, el método sincronizado a nivel de método se abandona y se cambia para que se cargue para cada archivo de clase ( nombre de ruta completo) corresponde a un bloqueo de objeto de objeto. Al bloquear, si el cargador tiene capacidades paralelas, entonces el cargador de clases ya no estará bloqueado, pero el bloqueo de objeto correspondiente al archivo de clase cargado se encontrará para la operación de bloqueo. Referencia: http://openjdk.java.net/projects/jdk7/features/#f352

Por ejemplo, regístrese así:

static {
		ClassLoader.registerAsParallelCapable();
	}

registerAsParallelCapable es un método estático de ClassLoader, que llamará al método ParallelLoaders.register, ParallelLoaders es una clase interna estática de ClassLoader, que contiene una propiedad estática de tipo Set, la operación de registro anterior le agregará el cargador de clases:

//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;
                }
            }
        }

Cuando se crea una instancia del cargador de clases, el constructor de la clase padre ClassLoader (todos los cargadores de clases personalizados heredarán directa o indirectamente de ClassLoader) determinará si el cargador actual tiene capacidades paralelas (si está en loaderTypes), si es así, luego inicializa un propiedad no estática paralelaLockMap de tipo CurrentHashMap para almacenar el objeto de bloqueo, la clave es el nombre del archivo de código de bytes que se va a cargar y el valor es el objeto de bloqueo del objeto:

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

En las definiciones de métodos que deben bloquearse en ClassLoade, la modificación sincronizada se elimina y el bloqueo se adquiere en el método y luego se sincroniza. Tome loadClass como ejemplo:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {...}
    }

Céntrese en el método 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;
    }

Se puede ver que si el cargador de clases tiene capacidades paralelas y se crea un paralelismoLockMap, se creará un objeto de bloqueo de objeto para cada className, de lo contrario se utilizará el cargador de clases en sí.

 

Supongo que te gusta

Origin blog.csdn.net/huangzhilin2015/article/details/114873607
Recomendado
Clasificación