虚拟机会保证一个类的< 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()会强制导致类的初始化。
三、类加载器