单例模式

1. 单例模式

定义:一个类只能有一个实例。

2. 单例模式几种写法:
饿汉模式
public class HungrySingleton {
    // static 保证了在类加载阶段就完成实例的初始化
    // fianl 确保对象的引用地址不被修改
    private static final HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {}

    public static HungrySingleton getInstance() {
        return instance;
    }
}

饿汉模式在类加载阶段就完成实例的初始化,保证了在同一个线程中多次调用getInstance()返回的对象是同一个。并且类加载机制也保证了在其在多线程中返回的对象也是同一个。

当执行getInstance()方法时会触发类加载(new、getstatic、putstatic、invokestatic会触发类加载)。当多个线程同时进行类加载时loadClass()方法里的synchronized保证了二进制文件只会加载一次。加载完成后会经历验证、准备、解析、初始化(为static字段初值),这一阶段完成了对象的创建。因此,饿汉模式在同一个线程或不同线程中获得的对象只有一个。

类加载机制使用synchronized关键字保证线程安全性。

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            ...
            
     }
懒汉模式
public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

这种懒汉模式在多线程下会创建多个对象,例如:

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
        	try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new LazySingleton();
        }
        return instance;
    }
}

改进一:可以使用synchronized关键字,对整个静态方法加锁。这种情况会使每次调用getInstance()的线程进行阻塞和切换会带一些不必要的开销,从而降低系统性能。改进二:双重检测锁模式,缩短锁的范围。

public class SyncSingleton {
    private static SyncSingleton instance;

    private SyncSingleton() {}

    public static synchronized SyncSingleton getInstance() {
        if (instance == null) {
            instance = new SyncSingleton();
        }
        return instance;
    }
}
双重检查锁模式
public class DoubleLockSingleton {
    // volatile的作用保证变量在多个线程中的可见性、防止指令的重排序
	private static volatile DoubleLockSingleton instance;

    private DoubleLockSingleton() {}

    public static DoubleLockSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleLockSingleton.class) {
                if (instance == null) {
                    instance = new DoubleLockSingleton();
                }
            }
        }
        return instance;
    }
}

instance = new DoubleLockSingleton()这一行代码在class文件中分为三步:

1.在堆上分配内存空间并返回一个指向该空间的内存引用

2.对空间进行初始化

3.把内存引用赋值给instance变量

在java内存模型中,1-2-3的执行顺序会发生改变,可能会变为1-3-2。

假设一个线程T1此时执行1-3-2,执行完1-3时,instance变量已经有一个引用值,这时一个线程T2执行第一个判断条件发现instance不为null,然后return返回结果,但此时instance指向的引用地址还没有进行空间初始化内容还是为null。

静态内部类
public class StaticInnerClassSingleton {
    private StaticInnerClassSingleton() {}
    
    static class InnerClass {
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }
    
    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.instance;
    }
}

静态内部类模式可以进行延迟加载,只用调用instance才会进行内部类的加载,同时也是使用jvm机制保证了线程安全性。

CAS实现单例
public class CASSingleton {

    private static final Unsafe unsafe = UnsafeInstance.getUnafe();

    private static volatile CASSingleton instance;
	// CASSingleton对象在内存中的偏移量
    private static final long instanceOffSet;

    static {
        try {
            instanceOffSet = unsafe.staticFieldOffset(CASSingleton.class.getDeclaredField("instance"));
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }

    private CASSingleton() {
    }

    public static CASSingleton getInstance() {
        // 自旋
        for (;;) {
            if (instance != null) {
                return instance;
            }
            instance = new CASSingleton();
            // 原子比较
            if (unsafe.compareAndSwapObject(instance, instanceOffSet, null, instance)) {
                return instance;
            }
        }
    }

    // 获取Unsafe对象
    static class UnsafeInstance {
        public static Unsafe getUnafe() {
            try {
                Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
                theUnsafeField.setAccessible(true);
                Object o = theUnsafeField.get(null);
                return (Unsafe) o;
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }
}

使用cas无锁机制保证单例模式的线程安全型,是使用v关键字的一种替换。Unsafe类不推荐使用,可以使用AtomicReference实现单例。

Atomic类实现单例
public class AtomicSingleton {
    public static final AtomicReference<AtomicSingleton> atomicSingleton = new AtomicReference<>();

    private AtomicSingleton() {}

    public static AtomicSingleton getInstance() {
        for (;;) {
            AtomicSingleton instance = atomicSingleton.get();
            if (instance != null) {
                return instance;
            }
            instance = new AtomicSingleton();
            if (atomicSingleton.compareAndSet(null, instance)) {
                return instance;
            }
        }
    }
}

AtomicReference的底层也是使用CAS无锁机制实现,相对于synchroinzed它没有线程阻塞和切换的额外消耗,可支持较大的并行度。如果有非常多的线程同时执行instance=new AtomicSingleton会创建大量的对象,有可能会导致内存溢出。

ThreadLocal实现单例
public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalSingletonInstance= 
            new ThreadLocal<ThreadLocalSingleton>() {
        @Override
        protected ThreadLocalSingleton initialValue() {
          return new ThreadLocalSingleton();  
        };
    };
    
    private ThreadLocalSingleton() {}
    
    public static ThreadLocalSingleton getInstance() {
        return threadLocalSingletonInstance.get();
    }
}

ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离多个线程对数据的访问冲突。相对于synchroinzed等同步机制仅提供一份变量,让不同的线程排队访问,ThreadLocal为每一个线程都提供一份变量,因此可以同时访问而不受影响。

使用ThreadLocal实现的单例,在同一个线程中创建的对象是同一个,但不同的线程中创建的对象不一样。

容器单例
public class ContainerSingleton {
    private static final Map<String, Object> singletonObject = new HashMap<>();

    private ContainerSingleton() {
    }

    public static void putInstance(String key, Object instance) {
        if (key != null && !key.isEmpty() && instance != null) {
            if (!singletonObject.containsKey(key)) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                singletonObject.put(key, instance);
            }
        }
    }

    public static Object getSingletonInstance(String key) {
        return singletonObject.get(key);
    }

    public static void main(String[] args) {
        new Thread(() -> {
            ContainerSingleton.putInstance("object", new Object());
            Object object = ContainerSingleton.getSingletonInstance("object");
            System.out.println(object);
        }).start();

        new Thread(() -> {
            ContainerSingleton.putInstance("object", new Object());
            Object object = ContainerSingleton.getSingletonInstance("object");
            System.out.println(object);
        }).start();
    }

}

使用双重检测锁保证安全性

public class ContainerSingleton {
    private static final Map<String, Object> singletonObject = new HashMap<>();

    private ContainerSingleton() {}

    public static void putInstance(String key, Object instance) {
        if (key != null && !key.isEmpty() && instance != null) {
            if (!singletonObject.containsKey(key)) {
                synchronized (ContainerSingleton.class) {
                    if (!singletonObject.containsKey(key)) {
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        singletonObject.put(key, instance);
                    }
                }
            }
        }
    }

    public static Object getSingletonInstance(String key) {
        return singletonObject.get(key);
    }

    public static void main(String[] args) {
        new Thread(() -> {
            ContainerSingleton.putInstance("object", new Object());
            Object object = ContainerSingleton.getSingletonInstance("object");
            System.out.println(object);
        }).start();

        new Thread(() -> {
            ContainerSingleton.putInstance("object", new Object());
            Object object = ContainerSingleton.getSingletonInstance("object");
            System.out.println(object);
        }).start();
    }
}

这种获取单例对象的方法可以参考Spring获取单例Bean,如果程序中单例类很多,可以考虑用一个容器管理起来。

枚举单例
public enum EnumSingleton {
    INSTANCE;
}

枚举单利可以防止多线程、序列化、反射的破坏。

通过javap反编译后的枚举代码

public final class com.dolores.design.EnumSingleton extends java.lang.Enum<com.dolores.design.EnumSingleton> {
  public static final com.dolores.design.EnumSingleton INSTANCE;
  public static com.dolores.design.EnumSingleton[] values();
  public static com.dolores.design.EnumSingleton valueOf(java.lang.String);
  public void doSomething();
  public static void main(java.lang.String[]);
  static {};
}

枚举反编译后可以发现其也是通过类加载机制保证的线程安全性。对于反序列和序列化枚举自己通过valueOf()保障了其安全性。

 public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum constant " + enumType.getCanonicalName() + "." + name);
    }

对于反射使用getDeclaredConstructors()获取全部的构造方法,可以发现其没有无参的构造方法,只有一个参数为(String.class,int.class)的父类的构造器,如果反射使用newInstance()则会报NoSuchMethodException的异常,如果使用父类的构造器则java.lang.IllegalArgumentException: Cannot reflectively create enum objects。通过源码可以发现如果该类是枚举则抛出异常。

 public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        // 检查该类是否被ENUM修饰  如果是则抛出异常
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
3. 单例模式的破坏

除了上文中的多线程,破坏单利模式的方式还有很多,常见的比如反射、序列化和反序列化。还可以通过Clone、多个类加载器以及分布式。

反射破坏单例模式

由于通过反射可以获取类的构造方法,所以通过反射可以创建出不同的对象。

public class ReflectDestroySingleton {
    private static final ReflectDestroySingleton instance = new ReflectDestroySingleton();

    private ReflectDestroySingleton() {}

    public static ReflectDestroySingleton getInstance() {
        return instance;
    }

    public static void main(String[] args) throws Exception {
        Constructor<ReflectDestroySingleton> constructor = ReflectDestroySingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        ReflectDestroySingleton instance = constructor.newInstance();
        ReflectDestroySingleton instance2 = constructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);
    }
}

解决办法是在构造函数中判断对象是否已经存在,如果存在则抛出异常。

public class ReflectDestroySingleton {
    private static final ReflectDestroySingleton instance = new ReflectDestroySingleton();

    private ReflectDestroySingleton() {
        synchronized (ReflectDestroySingleton.class) {
            if (instance != null) {
                throw new RuntimeException();
            }
        }
    }

    public static ReflectDestroySingleton getInstance() {
        return instance;
    }
}
序列化破坏单例
public class SerializeDestroySingleton implements Serializable {

    private static final SerializeDestroySingleton instance = new SerializeDestroySingleton();

    private SerializeDestroySingleton() {

    }

    public static SerializeDestroySingleton getInstance() {
        return instance;
    }

    public static void main(String[] args) throws Exception {
        SerializeDestroySingleton instance = SerializeDestroySingleton.getInstance();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp"));
        // 序列化
        out.writeObject(SerializeDestroySingleton.getInstance());
        ObjectInputStream input = new ObjectInputStream(new FileInputStream("temp"));
        // 烦序列化
        SerializeDestroySingleton instance2 = (SerializeDestroySingleton) input.readObject();
        System.out.println(instance);
        System.out.println(instance2);
    }

}

readObject()最终也是通过反射调用newInstance()实例化对象,所以每次调用产生的对象是不一致的。关键代码如下:

public final Object readObject() throws IOException, ClassNotFoundException {
	...
    // 调用这个方法
    Object obj = readObject0(false);
    ...
}

private Object readObject0(boolean unshared) throws IOException {
	...
    // 在调用这个方法readOrdinaryObject(unshared)
    case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));
    ...
}

private Object readOrdinaryObject(boolean unshared) throws IOException {
    ...
    obj = desc.isInstantiable() ? desc.newInstance() : null;
    ...
}

Object newInstance() throws InstantiationException, InvocationTargetException,
UnsupportedOperationException {
    ...
    if (cons != null) {
            try {
                if (domains == null || domains.length == 0) {
                    // 最终使用反射获得的构造函数创建对象
                    // cons 是Object类的构造方法
                    return cons.newInstance();
                } else {
   ...
}



同时,即使在构造函数中添加了之前解决反射破坏单例的方法,也不会起作用。

public class SerializeDestroySingleton implements Serializable {

    private static final SerializeDestroySingleton instance = new SerializeDestroySingleton();

    private SerializeDestroySingleton() {
        synchronized (SerializeDestroySingleton.class) {
            if (instance != null) {
                throw new RuntimeException();
            }
        }
    }

    public static SerializeDestroySingleton getInstance() {
        return instance;
    }

    public static void main(String[] args) throws Exception {
        SerializeDestroySingleton instance = SerializeDestroySingleton.getInstance();
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp"));
        // 序列化
        out.writeObject(SerializeDestroySingleton.getInstance());
        ObjectInputStream input = new ObjectInputStream(new FileInputStream("temp"));
        // 烦序列化
        SerializeDestroySingleton instance2 = (SerializeDestroySingleton) input.readObject();
        System.out.println(instance);
        System.out.println(instance2);
    }
}

这是因为反序列化的cons.newInstance()使用的是Object对象的构造函数,创建的对象是Object类的对象,通过强制转换可以变成需要的对象。

解决序列化和反序列破坏单例的方法是添加readResolve()方法,返回需要的对象。

public class SerializeDestroySingleton implements Serializable {

    private static final SerializeDestroySingleton instance = new SerializeDestroySingleton();

    private SerializeDestroySingleton() {
        synchronized (SerializeDestroySingleton.class) {
            if (instance != null) {
                throw new RuntimeException();
            }
        }
    }

    public static SerializeDestroySingleton getInstance() {
        return instance;
    }

    // 阻止反序列话破坏单例
    private Object readResolve(){
        return instance;
    }

使用这种方法可以解决问题的原因如下

private Object readOrdinaryObject(boolean unshared) throws IOException {
    ...
    if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())  // 判断对象是否实现readResolve方法 
        {
            Object rep = desc.invokeReadResolve(obj);  // 反射调用对象的readResolve方法
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
        	// 如果对象readResolve返回的对象与默认序列化对象不等,返回readResolve方法返回的对象
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
}
参考

单例与序列化的那些事儿

深入分析Java的序列化与反序列化

设计模式——单例模式的破坏

JAVA 枚举单例模式

潜谈单例模式

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值