设计模式——单例模式

单例模式是最简单的也是设计模式系列书籍开篇第一个讲到的模式,在平时的开发中也常常用它来保证获取的都是同一个实例。html

定义:确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例。java

饿汉模式

public class HungrySingleton {
    private static final HungrySingleton singleton = new HungrySingleton();

    //限制外部产生HungrySingleton对象
    private HungrySingleton(){ }

    //向外提供获取示例的静态方法
    public static HungrySingleton getInstance() {
        return singleton;
    }

    //other methods
}

饿汉模式是类加载时候就建立对象,利用了jvm特性保证了线程的安全性。linux

  • getInstance()方法是static的保证了经过类名可直接获取实例
  • 私有构造方法保证了只有本身才能够建立实例

懒汉模式

双重检查加锁 方式

 1 public class LazySingleton {
 2     private static volatile LazySingleton singleton = null;
 3 
 4     private LazySingleton() { }
 5 
 6     public static LazySingleton getSingleton() {
 7         if (singleton == null) { //不用每次获取对象都强制加锁,为了提高了效率
 8             synchronized (LazySingleton.class) {
 9                 if (singleton == null) {
10                     singleton = new LazySingleton();
11                 }
12             }
13         }
14         return singleton;
15     }
16 }
  • 第7行代码判空是为了提升效率,不用每次获取对象都强制加锁;
  • 第8行同步加锁是为了线程安全;
  • 第9行判空是为了保证单例对象的惟一性,只有没被建立才去建立。
  • volatile关键字为了保证singleton对象的可见性并禁止编译器对其进行编译指令重排序优化。

静态内部类 模式

内部类有用static修饰和不用static修饰的内部类:git

  • 用static修饰的是静态内部类,至关于其外部类的static成员,不依赖与外部类的实例,静态内部类只能够引用外部类的静态成员属性和静态方法,只在第一次使用到静态内部类的时候才会被装载
  • 不用static修饰的内部类称为对象级别内部类,依赖于外部的类实例
public class Singleton2 {
    /**
     * 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     * 没有绑定关系,并且只有被调用到时才会装载,从而实现了延迟加载。
     */
    private static class Singleton2Holder {
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static Singleton2 singleton = new Singleton2();
    }

    private Singleton2() {
        //System.out.println("singleton2 private construct method called");
    }

    public static Singleton2 getSingleton() {
        //System.out.println("singleton2 getSingleton method called");
        return Singleton2Holder.singleton;
    }

    private String name;

    public void desc() {
        //System.out.println("singleton2 desc method called");
    }
}

是饿汉模式的变种形式,利用jvm加载保证线程安全,而且实现了懒加载,只在获取实例的时候才去建立。github

JDK中单例的应用

Runtime单例实现

使用的是饿汉模式json

破坏单例模式

反射破坏单例模式

/**饿汉模式——反射建立对象*/
Class<HungrySingleton> singletonClass = HungrySingleton.class;
Constructor<HungrySingleton> singletonConstructor = singletonClass.getDeclaredConstructor();
singletonConstructor.setAccessible(true);
/**先反射建立*/
HungrySingleton hungrySingleton = singletonConstructor.newInstance();
/**再经过单例模式获取实例*/
HungrySingleton instance = HungrySingleton.getInstance();

System.out.println(hungrySingleton);
System.out.println(instance);

经过反射修改构造函数能够被访问,经过反射构造的结果和单例模式获取的不是同一个对象。设计模式

序列化破坏单例模式

HungrySingleton instance = HungrySingleton.getInstance();
jsonString = JSON.toJSONString(instance);
HungrySingleton singleton = JSON.parseObject(jsonString, HungrySingleton.class);
System.out.println(instance == singleton);

防止被破坏

防止反射建立对象

反射经过调用构造函数来建立对象,若是在构造函数里抛出异常,就能够组织反射建立对象(这种方式不适用与懒汉模式)。缓存

public class HungrySingleton {
    private static final HungrySingleton singleton = new HungrySingleton();

    //限制外部产生Singleton对象
    private HungrySingleton() {
        if (singleton != null) {
            throw new RuntimeException("不容许建立对象!");
        }
        System.out.println("singleton private construct method called");
    }

    //向外提供获取示例的静态方法
    public static HungrySingleton getInstance() {
        System.out.println("create singleton instance");
        return singleton;
    }
}

再用反射建立对象会报错安全

/**饿汉模式——反射建立对象*/
Class<HungrySingleton> singletonClass = HungrySingleton.class;
Constructor<HungrySingleton> singletonConstructor = singletonClass.getDeclaredConstructor();
singletonConstructor.setAccessible(true);
/**先反射建立*/
HungrySingleton hungrySingleton = singletonConstructor.newInstance();
/**再经过单例模式获取实例*/
HungrySingleton instance = HungrySingleton.getInstance();

System.out.println(hungrySingleton);
System.out.println(instance);

懒汉模式仍然会被破坏(当反射先于懒汉模式建立对象时,仍然会建立多个对象)jvm

/**懒汉模式——反射建立对象*/
Class<LazySingleton> lazySingletonClass = LazySingleton.class;
Constructor<LazySingleton> lazySingletonConstructor = lazySingletonClass.getDeclaredConstructor();
lazySingletonConstructor.setAccessible(true);
/**先经过反射获取实例*/
LazySingleton lazySingleton = lazySingletonConstructor.newInstance();
/**再经过单例模式获取实例*/
LazySingleton lazyInstance = LazySingleton.getInstance();

System.out.println(lazySingleton);
System.out.println(lazyInstance);

懒汉模式仍然会建立两个对象:

singleton.LazySingleton@593634ad 

singleton.LazySingleton@20fa23c1

防止反序列化建立对象

若是是使用ObjectInputStream方式序列化,可使用readResolve方法来控制。但序列化的方法有不少种,这种方式并不可靠。

枚举单例

《effective java》第77条:对于实例控制,枚举类型优于readResolve。

如下是一个枚举单例的示例

public enum EnumSingleton {
    INSTANCE;

    public String getDesc() {
        return "desc";
    }

    public static void process() {
        System.out.println("static process method");
    }

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}

使用时候直接获取 EnumSingleton.INSTANCE 便可获取当前实例,并且序列化与反序列化不会建立对象。

/**枚举获取实例*/
EnumSingleton instance = EnumSingleton.INSTANCE;
instance.setName("this is a enum singleton");
instance.setAge(28);
System.out.println("instance.name:"+instance.getName()+", instance.age:"+instance.getAge());

/**序列化*/
String jsonString = JSON.toJSONString(instance);
/**反序列化建立对象*/
EnumSingleton serializerInstance = JSON.parseObject(jsonString, EnumSingleton.class);
System.out.println(instance == serializerInstance);

Class<EnumSingleton> enumSingletonClass = EnumSingleton.class;
Constructor<EnumSingleton> enumSingletonConstructor = enumSingletonClass.getDeclaredConstructor();
enumSingletonConstructor.setAccessible(true);
/**反射建立*/
EnumSingleton enumSingleton = enumSingletonConstructor.newInstance();
System.out.println(enumSingleton);

输出:

 instance.name:this is a enum singleton, instance.age:28

true 

java.lang.NoSuchMethodException: singleton.EnumSingleton.<init>()

  at java.lang.Class.getConstructor0(Class.java:3082)

总结,实现单例模式的惟一推荐方法,使用枚举类来实现。

guava中的单例

 guava里有个Suppliers提供了memoize方法,用法以下:

Suppliers.memoize(new Supplier<Object>() {
            @Override
            public Object get() {
                return new Demo();
            }
        });

查看其实现源码,将传入的Suppliers做为代理传给MemoizingSupperlizer,返回一个类型为MemoizingSupperlizer类型的Supperlier对象;若是不是MemoizingSupperlizer类型,建立一个MemoizingSupperlizer实例返回:

MemoizingSupperlizer内部get方法使用double-check方式实现了只执行一次建立对象方法

一样的还有 ExpiringMemoizingSupplier 方法,支持过时时间只有再次调用get方法建立对象(能够用来实现缓存)

参考

代码示例

《设计模式之禅》https://www.cnblogs.com/shangxinfeng/p/6754345.htmlhttps://www.cnblogs.com/ttylinux/p/6498822.html?utm_source=itdadao&utm_medium=referralhttps://blog.csdn.net/hintcnuie/article/details/17968261https://www.cnblogs.com/ldq2016/p/6627542.html

相关文章
相关标签/搜索