类加载流程,类加载机制及自定义类加载器详解(面试再也不怕了) (2)

虚拟机会保证一个类的< clinit>方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的< clinit>方法,其他线程都需要阻塞等待,直到活动线程执行< clinit>方法完毕。

public class MultiThreadInitTest {
    static int A = 10;
    static {
           System.out.println(Thread.currentThread()+"init MultiThreadInitTest");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread() + "start");
            System.out.println(MultiThreadInitTest.A);
            System.out.println(Thread.currentThread() + "run over");
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
    }
}
//输出结果
//Thread[main,5,main]init MultiThreadInitTest
//Thread[Thread-0,5,main]start
//10
//Thread[Thread-0,5,main]run over
//Thread[Thread-1,5,main]start
//10
//Thread[Thread-1,5,main]run over

从输出中看出验证了:只有第一个线程对MultiThreadInitTest进行了一次初始化,第二个线程一直阻塞等待等第一个线程初始化完毕。

3.2、类初始化时机

当虚拟机启动时,初始化用户指定的主类;

当遇到用以新建目标类实例的new指令时,初始化new指令的目标类;

当遇到调用静态方法或者使用静态变量,初始化静态变量或方法所在的类;

子类初始化过程会触发父类初始化;

如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口初始化;

使用反射API对某个类进行反射调用时,初始化这个类;

Class.forName()会触发类的初始化

3.3、final定义的初始化

注意:对于一个使用final定义的常量,如果在编译时就已经确定了值,在引用时不会触发初始化,因为在编译的时候就已经确定下来,就是“宏变量”。如果在编译时无法确定,在初次使用才会导致初始化。

public class StaticInnerSingleton {
    /**
     * 使用静态内部类实现单例:
     * 1:线程安全
     * 2:懒加载
     * 3:非反序列化安全,即反序列化得到的对象与序列化时的单例对象不是同一个,违反单例原则
     */

    private static class LazyHolder {
        private static final StaticInnerSingleton INNER_SINGLETON = new StaticInnerSingleton();
    }

    private StaticInnerSingleton() {
    }

    public static StaticInnerSingleton getInstance() {
        return LazyHolder.INNER_SINGLETON;
    }
}

看这个例子,单例模式静态内部类实现方式。我们可以看到单例实例使用final定义,但在编译时无法确定下来,所以在第一次使用StaticInnerSingleton.getInstance()方法时,才会触发静态内部类的加载,也就是延迟加载。这里想指出,如果final定义的变量在编译时无法确定,则在使用时还是会进行类的初始化。

3.4、ClassLoader只会对类进行加载,不会进行初始化 public class Tester {
    static {
        System.out.println("Tester类的静态初始化块");
    }
}

class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        //下面语句仅仅是加载Tester类
        classLoader.loadClass("loader.Tester");
        System.out.println("系统加载Tester类");
        //下面语句才会初始化Tester类
        Class.forName("loader.Tester");
    }
}
//输出结果
//系统加载Tester类
//Tester类的静态初始化块

从输出证明:ClassLoader只会对类进行加载,不会进行初始化;使用Class.forName()会强制导致类的初始化。

三、类加载器

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyjjxs.html