一、单例模式的特点
属于创建型设计模式,用来创建对象实例的,
并且对象的实例是全局唯一的,所谓全局,是指在一个JVM虚拟机内存里面只有一个对象实例;
二、如何保证全局唯一?
外部不能new该对象(私有化构造器),如果外部需要使用,由该类提供外部访问该实例的方法;
在多线程并发的情况下,对象实例的依然是唯一的;
三、单例模式的实现:
1、利用JVM类加载机制实现(在JVM中Class的加载只加载一次)
优点:非常简单,并且在并发的情况下也不会有问题,利用JVM的类加载机制来保证对象实例的唯一性;
缺点:Class加载的时候就会创建该对象,即使没有那个方法使用该对象,也会进行加载,会造成资源浪费;(由于这个缺点,这种写法也被称为饿汉模式)
/**
* 利用JVM的类加载特性实现单例模式
*/
public class SingleInstance {
private static final SingleInstance INSTANCE = new SingleInstance();
private SingleInstance() {
}
public static SingleInstance getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(() -> {
SingleInstance instance = SingleInstance.getInstance();
System.out.println("hashcode:" + instance.hashCode());
});
thread.start();
}
}
}
2、利用静态内部类(静态内部类在外部类加载的时候不会被调用,只有在真正加载内部类的时候会被调用)的方式实现单例模式,能解决实例对象加载造成的资源浪费问题(因此也被称为懒汉模式(Lazy模式))
比较完美的写法:
public class SingleInstanceLazy {
private SingleInstanceLazy() {
}
static class SingleInnerInstance {
private static SingleInstanceLazy INSTANCE_LAZY = new SingleInstanceLazy();
public static SingleInstanceLazy getInstanceLazy() {
return INSTANCE_LAZY;
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(() -> {
SingleInstanceLazy instance = SingleInstanceLazy.SingleInnerInstance.getInstanceLazy();
System.out.println("hashcode:" + instance.hashCode());
});
thread.start();
}
}
}
3、不利用JVM类加载的方式也能创建,但要注意并发情况下可能导致的线程不安全问题而产生的对象实例不唯一问题:
public class SingleInstanceConcurrent {
/**
* 保证instanceConcurrent的可见性,即当instanceConcurrent发生变化时,其他对象能立即感知
* 禁止指令重排序,在new SingleInstanceConcurrent()的时候,按照申请内存、初始化、变量指针改变的顺序进行执行,保证instance在半初始化的时候也能唯一
*/
private static volatile SingleInstanceConcurrent instanceConcurrent;
private SingleInstanceConcurrent() {
}
/**
* 使用double check的方式保证对象是唯一的,并且效率也相对高些
*
* @return
*/
public static SingleInstanceConcurrent getInstance() {
if (instanceConcurrent == null) {
synchronized (SingleInstanceConcurrent.class) {
if (instanceConcurrent == null) {
instanceConcurrent = new SingleInstanceConcurrent();
}
}
}
return instanceConcurrent;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(() -> {
SingleInstanceConcurrent instance = SingleInstanceConcurrent.getInstance();
System.out.println("hashcode:" + instance.hashCode());
});
thread.start();
}
}
}
4、反射和反序列化对单例模式的破坏
通过反射的方式获取到的对象实例,下面代码结果输出false,说明获取到了两个对象实例:
public class SingleInstance {
private static final SingleInstance INSTANCE = new SingleInstance();
private SingleInstance() {
}
public static SingleInstance getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
Constructor<SingleInstance> constructor = SingleInstance.class.getDeclaredConstructor();
//安全检查,如果设置为true,允许通过反射的方式去构建对象实例
constructor.setAccessible(true);
SingleInstance instance1 = constructor.newInstance();
SingleInstance instance2 = SingleInstance.getInstance();
//输出false
System.out.println(instance1 == instance2);
}
}
反序列化对象对单例模式的破坏:
public class SingleInstance implements Serializable{
private static final SingleInstance INSTANCE = new SingleInstance();
private SingleInstance() {
}
public static SingleInstance getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
//将instance对象输出到文件,SingleInstance必须实现序列化接口
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("aaa.txt"));
objectOutputStream.writeObject(SingleInstance.getInstance());
//读取instance对象
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("aaa.txt"));
//通过跟踪objectInputStream.readObject()方法,发现反序列化,也是通过反射的方式去重新构建对象的,因此会返回false
SingleInstance instance = (SingleInstance) objectInputStream.readObject();
//结果输出false
System.out.println(instance == SingleInstance.getInstance());
}
}
5、如何解决反射或者反序列化对单例模式的破坏?使用枚举的方式去创建单例
public enum SingleInstanceEnum {
INSTANCE_ENUM;
public static SingleInstanceEnum getInstance() {
return INSTANCE_ENUM;
}
public static void main(String[] args) throws Exception {
System.out.println(SingleInstanceEnum.getInstance() == SingleInstanceEnum.INSTANCE_ENUM);
//反射的方式,会抛异常,枚举不允许通过反射的方式去构建对象
Constructor<SingleInstanceEnum> constructor = SingleInstanceEnum.class.getDeclaredConstructor();
//安全检查,如果设置为true,允许通过反射的方式去构建对象实例
constructor.setAccessible(true);
SingleInstanceEnum instance1 = constructor.newInstance();
SingleInstanceEnum instance2 = SingleInstanceEnum.getInstance();
//输出false
System.out.println(instance1 == instance2);
}
}
总结:在实际使用过程中,一般通过Spring的Bean工厂来生成一个单利的对象,自己手动去实现单利的情况比较少;