咱们都晓得,饿汉式单例是线程平安的,也就是不会初始化的时候创立出两个对象来,然而为什么呢?
首先定义一个饿汉式单例如下:
public class Singleton {
// 私有化构造方法,以避免外界应用该构造方法创立新的实例
private Singleton(){
}
// 默认是public,拜访能够间接通过Singleton.instance来拜访
static Singleton instance = new Singleton();
}
之所以是线程平安的,是因为JVM在类加载的过程,保障了不会初始化多个static
对象。类的生命周期次要是:
加载–>验证–>筹备–>解析–>初始化–>应用–>卸载
下面的代码,实际上类成员变量instance
是在初始化阶段的时候实现初始化,所有的类变量以及static
动态代码块,都是在一个叫clinit()
的办法外面实现初始化。这一点,应用jclasslib
能够看进去:
clinit()
办法是由虚拟机收集的,蕴含了static
变量的赋值操作以及static
代码块,所以咱们代码中的static Singleton instance = new Singleton();
就是在其中。虚拟机自身会保障clinit()
代码在多线程并发的时候,只会有一个线程能够拜访到,其余的线程都须要期待,并且等到执行的线程完结后才能够接着执行,然而它们不会再进入clinit()
办法,所以是线程平安的。咱们能够验证一下:
首先革新一下单例:
public class Singleton {
// 私有化构造方法,以避免外界应用该构造方法创立新的实例
private Singleton() {
}
// 默认是public,拜访能够间接通过Singleton.instance来拜访
static Singleton instance = null;
static {
System.out.println("初始化static模块---开始");
instance = new Singleton();
try {
System.out.println("初始化中...");
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("初始化static模块----完结");
}
}
测试代码:
import java.io.*;
import java.lang.reflect.InvocationTargetException;
public class SingletonTests {
public static void main(String[] args) throws Exception, InvocationTargetException, InstantiationException, NoSuchMethodException {
Thread thread1 = new Thread(new Runnable() {
public void run() {
System.out.println("线程1开始尝试初始化单例");
Singleton singleton = Singleton.instance;
System.out.println("线程1获取到的单例:" + singleton);
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
System.out.println("线程2开始尝试初始化单例");
Singleton singleton = Singleton.instance;
System.out.println("线程2获取到的单例:" + singleton);
}
});
thread1.start();
thread2.start();
}
}
运行后果,一开始运行的时候,咱们能够看到线程1进去了static
代码块,它在初始化,线程2则在期待。
待到线程1初始化实现的时候,线程2也不会再进入static
代码块,而是和线程1获得同一个对象,由此可见,static
代码块实际上就是线程平安的。
发表回复