懒汉模式(比如Spring的ReactiveAdapterRegistry类)
class LazySingleTon {
private static LazySingleTon instance;
private LazySingleTon() {
}
public static LazySingleTon getInstance() {
if(instance==null) {
try{
Thread.sleep(200);
}catch(InterruptedException e) {
e.printStackTrace();
}
instance = new LazySingleTon();
}
return instance;
}
}
但是这样多线程访问有可能出现多实例问题,测试代码如下:
new Thread( ()->{
LazySingleTon instance = LazySingleTon.getInstance();
System.out.println(instance);
}).start();
new Thread( ()->{
LazySingleTon instance = LazySingleTon.getInstance();
System.out.println(instance);
}).start();
这样发现两个instance并不一样,所以getInstance方法要加上synchronized
public synchronized static LazySingleTon getInstance() {
...
}
但是加锁的操作有性能的损耗,那么可以修改成
class LazySingleTon {
private static LazySingleTon instance;
private LazySingleTon() {
}
public static LazySingleTon getInstance() {
if(instance==null) {
synchronized (LazySingleTon.class) {
if(instance==null) {
instance = new LazySingleTon();
// 字节码层面
// JIT、CPU
// 1.分配空间
// 2.初始化
// 3.引用赋值
}
}
}
return instance;
}
}
但是通过javapp -v xxx.class可见,new对象的时候可能发生重排序,如果初始化和赋值颠倒,可能第二个进入的线程拿到的是空对象引发空指针,所以要加上volatile,如下:
class LazySingleTon {
private volatile static LazySingleTon instance;
private LazySingleTon() {
}
public static LazySingleTon getInstance() {
if(instance==null) {
synchronized (LazySingleTon.class) {
if(instance==null) {
instance = new LazySingleTon();
}
}
}
return instance;
}
}
总结:
1、线程安全问题
2、double check加锁优化
3、编译器JIT、CPU可能对指令进行重排序,导致使用到未初始化的实例,可以通过添加volatile进行修饰防止指令重排
饿汉模式(比如jdk的Runtime类)
class HungrySingleTon {
private static HungrySingleTon instance = new HungrySingleTon();
private HungrySingleTon() {
}
public static HungrySingleTon getInstance() {
return instance;
}
}
通过jvm类加载机制,保证实例的唯一性,类加载过程如下:
1、加载二进制数据到内存中,生成对应的Class数据结构;
2、连接:a.验证 b.验证并给静态成员变量赋默认值 c.解析
3、初始化:给类的静态变量赋初值
静态内部类
class InnerClassSingleton{
private static class InnerClassHolder{
private static InnerClassSingleton instance = new InnerClassSingleton();
}
private InnerClassSingleton() {
}
public static InnerClassSingleton getInstance() {
return InnerClassHolder.instance;
}
}
本质上是利用类的加载机制来保证线程安全,只有在实际使用的时候才会触发类的初始化,所以也是懒加载的一种。
反射破坏
Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
InnerClassSingleton instance = InnerClassSingleton.getInstance();
System.out.println(innerClassSingleton == instance); //false
改造私有构造器
private InnerClassSingleton() {
if(InnerClassSingleton.instance!=null) {
throw new RuntimeException("Forbidden!");
}
}
反序列化破坏(比如jdk的Currency类)
InnerClassSingleton instance = InnerClassSingleton.getInstance();
ObjectOutStream oos = new ObjectOutStream(new FileOutStream("testSerializable"));
oos.writeObject(instance);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
InnerClassSingleton object = ((InnerClassSingleton)ois.readObject());
System.out.println(instance==object); //false
实现Object readResolve() throws ObjectStreamException方法,注意反序列化时需要指定版本号,static final long serialVersionUID
Object readResolve() throws ObjectStreamException {
return InnerClassHolder.instance;
}