Druid source code analysis: some tips in initialization methods

1, Double detection lock

When initializing the thread pool, in order to ensure concurrency safety and initialization performance, the double detection lock is used for initialization. The code is as follows,

The first step is to ensure efficiency: after all, initialization only calls the initialization logic when the connection pool is not initialized or has not been initialized. In most scenarios, the connection pool has been initialized. Therefore, if you do not add the first judgment on whether the initialization has been completed, you need to lock as long as you obtain the connection, which will affect the performance.

The second step of locking is to ensure concurrency safety: if multiple threads pass the first judgment without initialization, they will all be initialized, resulting in thread safety. Therefore, locking can prevent multiple threads from initializing the connection pool at the same time.

The third step is to ensure concurrency safety: if multiple threads pass the first judgment

The use of volatile ensures the visibility of the variable initiated. If it is modified to ensure that other threads are visible, it will not cause thread safety problems because a thread does not update the local variable to the shared memory after updating.

protected volatile boolean inited = false;

if (inited) {
            return;
        }

        // Get driver
        // bug fixed for dead lock, for issue #2980
        DruidDriver.getInstance();

        // Lock to prevent repeated initialization
        final ReentrantLock lock = this.lock;
        try {
            lock.lockInterruptibly();
        } catch (InterruptedException e) {
            throw new SQLException("interrupt", e);
        }

        boolean init = false;
        try {
            if (inited) {
                return;
            }
    ......

 

2, Concurrency control

Firstly, the double detection lock has been analyzed above, so we won't analyze it anymore.

Create connection asynchronously:

Thread pools were created using the ScheduledExecutorService to create connections. Then, in the submitted Task "CreateConnectionTask", the "ReentrantLock" lock processing is used.

    private void submitCreateTask(boolean initTask) {
        createTaskCount++;
        CreateConnectionTask task = new CreateConnectionTask(initTask);
        if (createTasks == null) {
            createTasks = new long[8];
        }

        boolean putted = false;
        for (int i = 0; i < createTasks.length; ++i) {
            if (createTasks[i] == 0) {
                createTasks[i] = task.taskId;
                putted = true;
                break;
            }
        }
        if (!putted) {
            long[] array = new long[createTasks.length * 3 / 2];
            System.arraycopy(createTasks, 0, array, 0, createTasks.length);
            array[createTasks.length] = task.taskId;
            createTasks = array;
        }

        this.createSchedulerFuture = createScheduler.submit(task);
    }

 

The variable "initedLatch" modified by CountDownLatch is used to report that the thread created concurrently has been executed until this step.

3, SPI mechanism

SPI mechanism is a common extension mechanism for many open source components. For example, it is widely used in JDK, dubbo, SpringBoot and Spring Cloud. SPI mechanism is also used in Druid to load all configured filters to achieve pluggable Filter configuration.

    private void initFromSPIServiceLoader() {
        // Skip use spi load filter
        if (loadSpifilterSkip) {
            return;
        }

        if (autoFilters == null) {
            List<Filter> filters = new ArrayList<Filter>();
            ServiceLoader<Filter> autoFilterLoader = ServiceLoader.load(Filter.class);

            for (Filter filter : autoFilterLoader) {
                AutoLoad autoLoad = filter.getClass().getAnnotation(AutoLoad.class);
                if (autoLoad != null && autoLoad.value()) {
                    filters.add(filter);
                }
            }
            autoFilters = filters;
        }

        for (Filter filter : autoFilters) {
            if (LOG.isInfoEnabled()) {
                LOG.info("load filter from spi :" + filter.getClass().getName());
            }
            addFilter(filter);
        }
    }

 

From the above code, we can see that it loads the implementation class of Filter interface through SPI mechanism,

    public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

    public static <S> ServiceLoader<S> load(Class<S> service,
                                            ClassLoader loader)
    {
        return new ServiceLoader<>(service, loader);
    }

    private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
    }

    public void reload() {
        providers.clear();
        lookupIterator = new LazyIterator(service, loader);
    }

 

 

It can be seen that a lazy iterator, lazyitterer, is finally created. In fact, the SPI configuration class will be loaded only when it is used.

In the iterator ¢ lazyitterer, you can see that there is a ¢ hasNextService method, which actually handles the loading of SPI.

private static final String PREFIX = "META-INF/services/";

    private class LazyIterator
        implements Iterator<S>
    {

        Class<S> service;
        ClassLoader loader;
        Enumeration<URL> configs = null;
        Iterator<String> pending = null;
        String nextName = null;

        private LazyIterator(Class<S> service, ClassLoader loader) {
            this.service = service;
            this.loader = loader;
        }

        private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }

In the hasNextService method, first obtain the full path name, that is, the Filter implementation under the # META-INF/services / directory, and then load it through the class loader. If no class loader is passed in, call # classloader Getsystemresources generates an enumeration < URL > object according to the fully qualified name. If the incoming class loader has a specified class loader, the instance will be loaded according to the fully qualified name with the specified class loader.

4, Class loader

In the above code, you can see the relevant information of class loading. If no class loader is passed in, the resource class loader of the system will be used for loading. In the getSystemResources() method, first call the getSystemClassLoader() method to obtain a class loader. If it is successful, call the getResources() method of the class loader to load the class. If it is not obtained, Use getbootstrap resources (name) to load using the boot class loader.

    public static Enumeration<URL> getSystemResources(String name)
        throws IOException
    {
        ClassLoader system = getSystemClassLoader();
        if (system == null) {
            return getBootstrapResources(name);
        }
        return system.getResources(name);
    }

 

1. Get class loader

First, let's take a look at the method getSystemClassLoader() to get the system class loader. Because the ClassLoader is implemented inside the JVM, it can't be obtained in Java code, but sun. Can be used misc. Launcher, you can see that the following code uses sun misc. Launcher. The getClassLoader() method of getlauncher () gets the class loader.

If there is a security manager, and the class loader of the caller is not empty, and the class loader of the caller is different from the ancestor of the system class loader, this method uses RuntimePermission("getClassLoader" calls the checkPermission method of the security manager) to verify the access to the system class loader.

    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }

    private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }

 

2. Find resources

If the class loader obtained in the previous step is not empty, call the getResources() method to find all resources with the given name. You can see that it defines an enumeration array of URL objects with a length of 2. If there is a parent class loader in the current class loader, call the method of the parent class loader recursively. If there is no parent class loader, If the current class loader is the boot class loader, call getbootstrap resources (name).

In addition, I called {findResources(name) to find resources from the current class loader.

In fact, if there is a specified class loader passed in, it is also the class loading done by the called getResources method. If the class loader obtained in the first step is empty, it is also the getbootstrap resources (name) method in this step that is called to start the class loader to load resources.

    public Enumeration<URL> getResources(String name) throws IOException {
        @SuppressWarnings("unchecked")
        Enumeration<URL>[] tmp = (Enumeration<URL>[]) new Enumeration<?>[2];
        if (parent != null) {
            tmp[0] = parent.getResources(name);
        } else {
            tmp[0] = getBootstrapResources(name);
        }
        tmp[1] = findResources(name);

        return new CompoundEnumeration<>(tmp);
    }

 

3. Find resources from the built-in class loader of VM

In the previous step, you called the getbootstraphresources (name) method to find resources from the VM's built-in classloader. The code is as follows. In fact, if the class loader obtained in the first step is empty, the getbootstrap resources (name) method in this step is also called to find resources from the VM's built-in class loader.

    private static Enumeration<URL> getBootstrapResources(String name)
        throws IOException
    {
        final Enumeration<Resource> e =
            getBootstrapClassPath().getResources(name);
        return new Enumeration<URL> () {
            public URL nextElement() {
                return e.nextElement().getURL();
            }
            public boolean hasMoreElements() {
                return e.hasMoreElements();
            }
        };
    }

 

5, Reflection class loading

When dealing with driver related configurations, create instances of driver classes according to different drivers.

First, it judges whether the driver class attribute , driverClass is set. If not, it sets the , driverClass attribute using the simple factory mode according to the JDBC URL, and then judges whether it is , MockDriver, BalancedClickhouseDriver or others according to the , driverClass attribute, and loads the class respectively.

If it is a # MockDriver, it uses static variables to initialize the class, which ensures that MockDriver is a singleton.

public final static MockDriver         instance              = new MockDriver();

 

If it is a BalancedClickhouseDriver, the loading done by directly calling the constructor is multiple

    public BalancedClickhouseDriver(final String url, Properties properties) {
        this.url = url;
        this.dataSource = new BalancedClickhouseDataSource(url, properties);
    }

    public BalancedClickhouseDataSource(String url, Properties properties) {
        this(splitUrl(url), new ClickHouseProperties(properties));
    }

 

If it is not the above two drivers, use the {classloader Loadclass or class Forname loads the class and generates an instance using newInstance().

    public static Driver createDriver(ClassLoader classLoader, String driverClassName) throws SQLException {
        Class<?> clazz = null;
        if (classLoader != null) {
            try {
                clazz = classLoader.loadClass(driverClassName);
            } catch (ClassNotFoundException e) {
                // skip
            }
        }

        if (clazz == null) {
            try {
                ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
                if (contextLoader != null) {
                    clazz = contextLoader.loadClass(driverClassName);
                }
            } catch (ClassNotFoundException e) {
                // skip
            }
        }

        if (clazz == null) {
            try {
                clazz = Class.forName(driverClassName);
            } catch (ClassNotFoundException e) {
                throw new SQLException(e.getMessage(), e);
            }
        }

        try {
            return (Driver) clazz.newInstance();
        } catch (IllegalAccessException e) {
            throw new SQLException(e.getMessage(), e);
        } catch (InstantiationException e) {
            throw new SQLException(e.getMessage(), e);
        }
    }

 

  

 

Tags: Druid

Posted by starrieyed on Fri, 13 May 2022 09:02:47 +0300