单例模式以及如何强化单例属性

特点
单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这个实例。
优点
提供了对唯一实例的受控访问;
由于系统内存中只存在一个对象,因此可节约系统的资源,对于一些频繁的创建和销毁的对象,单例模式可以提升系统的性能。
单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例在内存中,避免了对同一个资源文件的重复写操作。
单例模式可以在系统设置全局的访问点,优化和共享访问,例如:设计一个单例类,负责所有数据表的映射处理。
缺点
单例模式一般没有接口,扩展很困难,如果要扩展,则必须修改代码。
单例类职责过重,在一定的程度上违背了单一职责。
滥用单例模式会带来一些负面问题。如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
饿汉式单例类

 1 public class EagerSingleton{
 2     private static EagerSingleton instance = new EagerSingleton();
 3     /**
 4     *私有默认构造
 5     */
 6     private EagerSingleton(){}
 7     //静态工厂方法
 8     public static EagerSingleton getInstance(){
 9         return instance;
10     }
11 }

饿汉式单例模式,会在这个类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,这时候,实例就被创建出来了,除非系统重启,否则这个对象不会改变,所以本身就是线程安全的。
饿汉式是典型的空间换时间,类加载的时候就创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。

懒汉式单例类

public class LazySingleton{
    private static LazySingleton instance = null;
    //私有默认构造函数
    private LaySingleton(){}
    //静态工厂方法
    public static synchronized LazySingleton getInstance(){
        if(instance == null){
            instance = new LazySingleton();
        }
        return instance;
     }
}        

懒汉式单例类对静态工厂方法使用了同步化,以处理多线程环境,此方式虽然解决了同步问题,但是该方式效率比较低下,下一个线程想要获取对象,就必须等上一个线程释放锁之后,才可以去获取。
懒汉式会在需要使用实例的时候才创建实例。
懒汉式是典型的时间换空间,就是每次实例都会判断,看是否需要创建实例,浪费了判断的时间,当然,如果一直没有人使用的话,就不会创建实例,节约了内存空间(但是没用,你建它干嘛)。
双重检验加锁模式

public class Sington{
    private volatile static Singleton instance = null;
    private Singleton(){}

    public static Singleton getInstance(){
    //先检查实例是否存在,如果不存在才进入下面的同步块
        if(instance == null){
            synchronized(Singleton.class){
            //再次检查实例是否存在,如果不存在再创建实例
            if(instance == null){
                instance = new Singleton();
                }
            }
        }
        return instance;
     }
}        

这种模式既可以保证线程安全的创建实例,而又不会对性能造成太大的影响。只会在第一次创建的时候同步,以后就都不会同步了,从而加快了运行速度。
提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行的效率不会很高。

静态内部类模式

这种模式综合使用了Java的类级内部类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全。
相应的基础知识:
❤ 什么是类级内部类?
  简单来说,类级内部类就是有static修饰的成员内部类,如果没有sattic修饰的成员内部类被称为对象级内部类。
  类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可以直接创建。而对象级的内部类的实例,是绑定在外部对象实例中的。
  类级内部类中,可以定义静态的方法,在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
  内级内部类相当于其外部类的成员,只有在第一次被使用的时候才会被装载。
❤ 多线程缺省同步锁知识
  在多线程开发中,为了解决并发问题,主要是通过加锁的方式来进行同步控制,但是在一些情况中,JVM已经隐含地为你执行了同步,这些情况下就不用自己再来进行同步控制了,这些情况包括:
  1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
  2.访问final字段时
  3.在创建线程之前创建对象时
  4.线程可以看见它将要处理的对象时

public class InnerSingleton {
    //私有构造方法
    private InnerSingleton(){
        
    }
    /**
     * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系,
     * 而且只有被调用时才会被装载,从而实现了延迟加载
     */
    private static class InnerInSingleton{
        //静态的初始化器,由jvm来保证线程安全
        private static InnerSingleton singleton = new InnerSingleton();
    }
    //调用实例
    public static InnerSingleton getSingleton(){
        return InnerInSingleton.singleton;
    }
}

当getInstance方法第一次被调用的时候,它第一次读取InnerInSingleton.singleton,导致InnerInSingleton类得到初始化;这种方式是Singleton类被装载了,singleton实例不一定被初始化。因为InnerInSingleton类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载InnerInSingleton类,从而实例化singleton。而这个类在装载并初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全。

这种模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

以上四种单例模式,并不能严格保证全局只有一个对象。

可以通过反射机制,设置AccessibleObject.setAccexxible(true),改变构造器的访问属性,调用构造器生成新的实例。
例1: 以调用内部静态类的单例模式为例

public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        InnerSingleton singleton = InnerSingleton.getSingleton();
        
        //getDeclaredConstructor:返回参数类型的所有构造器,包括public的和非public的,当然也包括private的。
        Constructor<InnerSingleton> constructor = InnerSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        InnerSingleton singleton2 = constructor.newInstance();
        System.out.println(singleton == singleton2);
    }

输出:

false

表明并不是同一个实例。
要防止这种情况,可以修改构造器,在第二次创建实例的时候抛出异常。
例2:还是以内部静态类的单例模式为例:

public class InnerSingleton {
    //定义count为全局静态变量
    private static int count = 0;
    //私有构造方法
    private InnerSingleton(){
        if(count > 0){
            throw new IllegalArgumentException("不能创造InnerSingleton两次!");
        }
        count++;
    }
    /**
     * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系,
     * 而且只有被调用时才会被装载,从而实现了延迟加载
     */
    private static class InnerInSingleton{
        //静态的初始化器,由jvm来保证线程安全
        private static InnerSingleton singleton = new InnerSingleton();
    }
    
    //调用实例
    public static InnerSingleton getSingleton(){
        return InnerInSingleton.singleton;
    }
}

再运行用例1 的代码,则会报错:

Exception in thread "main" java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    at com.qrTest.singleton.Test.main(Test.java:14)
Caused by: java.lang.IllegalArgumentException: 不能创造InnerSingleton两次!
    at com.qrTest.singleton.InnerSingleton.<init>(InnerSingleton.java:10)
    ... 5 more

解决了多次实例化的问题。

当单例模式需要序列化的时候,新的问题又来了;
上面的几种单例模式都是可以序列化的,实现Serializable就可以实现序列化,为了保证序列化的时候实例还是Singleton,必须声明所有的实例域都是transient的,并且提供 readResolve方法,否则,每次反序列化都会生成新的实例。
例3:以内部静态类单例模式为例

public class InnerSingleton implements Serializable{
    //私有构造方法
    private InnerSingleton(){
        
    }
    /**
     * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系,
     * 而且只有被调用时才会被装载,从而实现了延迟加载
     */
    private static class InnerInSingleton{
        //静态的初始化器,由jvm来保证线程安全
        private static InnerSingleton singleton = new InnerSingleton();
    }
    
    //调用实例
    public static InnerSingleton getSingleton(){
        return InnerInSingleton.singleton;
    }
    
    //测试
    public static void main(String[] args) throws Exception, IOException {
        InnerSingleton singleton = InnerSingleton.getSingleton();
        
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
        oos.writeObject(singleton);
        oos.close();
        
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
        InnerSingleton singleton2 = (InnerSingleton) ois.readObject();
        ois.close();
        
        System.out.println(singleton == singleton2);
    }
}

输出:

false

加入 readResolve 方法后:

public class InnerSingleton implements Serializable{
    //私有构造方法
    private InnerSingleton(){
        
    }
    /**
     * 静态内部类:类级的内部类,该内部类的实例和外部类的实例没有绑定关系,
     * 而且只有被调用时才会被装载,从而实现了延迟加载
     */
    private static class InnerInSingleton{
        //静态的初始化器,由jvm来保证线程安全
        private static InnerSingleton singleton = new InnerSingleton();
    }
    
    //调用实例
    public static InnerSingleton getSingleton(){
        return InnerInSingleton.singleton;
    }
    
    //加入readResolve方法
    public Object readResolve() {
        return InnerSingleton.getSingleton();
    }
    
    //测试
    public static void main(String[] args) throws Exception, IOException {
        InnerSingleton singleton = InnerSingleton.getSingleton();
        
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
        oos.writeObject(singleton);
        oos.close();
        
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
        InnerSingleton singleton2 = (InnerSingleton) ois.readObject();
        ois.close();
        
        System.out.println(singleton == singleton2);
    }
}

输出:

true

通过此方法保证了反序列化单例的唯一。

单例类和枚举
单元素的枚举类型已经成为Singleton的最佳方法。

public enum Singleton{
    //定义一个枚举元素,它就代表了Singleton的一个实例
    Instance;
    //单例也可以有自己的操作
    public void singletonOpt(){
        //操作
    }
}

使用枚举来实现单实例的控制会更加的简洁,而且无偿的提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化;

枚举类型也可以防止反射攻击,当你试图通过反射去实例化一个枚举类型的时候抛出IllegalArgumentException:Cannot reflectively create enum objects 异常,是更加简洁、高效、安全的实现单例的方式。

 

此篇文章参考:https://www.cnblogs.com/java-my-life/archive/2012/03/31/2425631.html,LZ个人觉得这个博主的java设计模式图文并茂讲解的非常好;欢迎大家去阅读。

 

posted on 2018-09-12 15:49  AoTuDeMan  阅读(713)  评论(0编辑  收藏  举报

导航