设计模式|完全理解单列模式

单例模式是一种经常使用的设计模式、也多是设计模式中代码量最少的设计模式。可是少并不意味着简单、想要用好、用对单例、就的费一番脑子了。由于它里面涉及到了不少Java底层的知识如类装载机制、Java内存模型、volatile等知识点。数据库

简介

单例模式属于23中设计模式中的建立型模式、定义是确保某一个类只有一个实例、并提供一个全局的访问点。设计模式

具备如下3个特性:
  • 只能有一个实例
  • 必须本身建立本身惟一实例
  • 提供全局访问点

基本实现思路

单例要求类只能返回同一对象的引用、必须提供一个静态获取该实例的方法
实现能够经过如下两步:安全

  • 私有化构造方法、防止外部实例化、只有经过对外提供的静态方法来获取惟一实例
  • 提供一个静态方法获取对象的实例。

单例的7种实现方式

1. 饿汉式
public class EagetSingleton {

    private static final EagetSingleton INSANCE = new EagetSingleton();

    // 私有化构造函数、防止外部实例化
    private EagetSingleton() {
    }

    // 提供静态外部访问方法
    public static EagetSingleton getInstance() {
        return INSANCE;
    }

}

优势:写法简单、类装载时就实例化了静态变量、避免了线程并发问题。
缺点:在类装载过程当中就实例化了对象、形成了资源浪费。多线程

2. 饿汉式(静态代码块)
public class StaticBlockSingleton {

    private static StaticBlockSingleton INSTANCE = null;

    static {
        try {
            INSTANCE = new StaticBlockSingleton();
        } catch (Exception e) {
        }
    }

    // 私有化构造函数、防止外部实例化
    private StaticBlockSingleton() {
    }

    // 提供静态外部访问方法
    public static StaticBlockSingleton getInstance() {
        return INSTANCE;
    }

}

这种方式和上述实现方式基本相同、只是把类实例化的过程放到了静态代码块中来实例化、一样也是在类装载过程执行静态代码块、优缺点基本相同可是它能够在类实例化过程当中作一些额外的操做如异常处理等。并发

3. 懒汉式(线程不安全)
public class LazySingleton {

    private static LazySingleton INSTANCE = null;

    // 私有化构造函数、防止外部实例化
    private LazySingleton() {
    }

    // 提供静态外部访问方法
    public static LazySingleton getInstance() {
        if (null == INSTANCE) { -------- 1
            INSTANCE = new LazySingleton(); ------2
        }
        return INSTANCE;
    }

}

优势:实现了懒加载、避免了资源的浪费。
缺点:线程不安全、在多线程状况下当一个线程执行到 1 处的时候、尚未来得及往下执行另外一个线程也到 1 处 这样两个线程同时执行 2 处代码、破坏了单例。函数

4. 懒汉式(加锁)
public class LazySyncSingleton {

    private static LazySyncSingleton INSTANCE = null;

    // 私有化构造函数、防止外部实例化
    private LazySyncSingleton() {
    }

    // 效率低下
    // 提供静态外部访问方法
    public static synchronized LazySyncSingleton getInstance() {

        if (null == INSTANCE) {
            INSTANCE = new LazySyncSingleton();
        }

        return INSTANCE;
    }
}

解决了3中线程不安全的问题、利用synchronized对getInstance()方法加锁以达到同步访问。
优势:线程同步
缺点:效率低下、此方式对整个对象加锁、每次访问getInstance() 都须要同步访问、这种状况多线程并发效率很是低下、其实咱们只须要在对象还没实例化前加锁就能够了、实例化后就不存在并发问题了。工具

5. 懒汉式(双重锁)
public class DCheckSingleton {
    private static volatile DCheckSingleton INSTANCE = null;

    // 私有化构造函数、防止外部实例化
    private DCheckSingleton() {
    }

    // 提供静态外部访问方法
    public static DCheckSingleton getInstance() {

        if (null == INSTANCE) {
            synchronized (DCheckSingleton.class) {
                if (null == INSTANCE) {
                    INSTANCE = new DCheckSingleton();
                }
            }
        }
        return INSTANCE;
    }
}

解决了4中并发状况下效率低下的问题。
优势:线程安全、延迟加载、效率高
涉及到知识点: 1:volatile 关键字 确保内存的可见性和有序性。若是不加volatile关键字会有什么状况? 我知道在对象实例化时INSTANCE = new DCheckSingleton();这一句代码JVM中并非一步执行的而是分为三步(1)在栈内存中为 建立对象的引用指针 INSTANCE (2)在堆内存中开辟一块空间来存放实例化的对象 new DCheckSingleton(); (3)将INSTANCE指向堆内存空间地址J、VM只保证了代码执行结果的正确性、并不保证执行顺序(这里涉及到Java内存模型知识点在这就很少说了、感兴趣的同窗能够去了解下JVM一些底层实现原理)因此 1 ,2,3三步也多是1 ,3 ,2 这样咱们就可能拿到的时一个半成品的对象了。
2: 涉及到类实例化知识点
3: 涉及到Java内存模型
4:涉及到JVM的一些执行优化、指令重排等性能

6. 静态类部类
public class InnerSingleton {

    private  InnerSingleton() {
    }
    
    public static InnerSingleton getInstance() {
        return InnerClassSingleton.INSTANCE;
    }
    
    private static class InnerClassSingleton{
        private static final InnerSingleton INSTANCE = new InnerSingleton();
    }
}

这种方式和饿汉式的实现机制基本相同、都是利用了类装载机制来保证线程的安全、它和饿汉式的惟一区别就是实现了懒加载的机制、只有在调用getInstance()方法时才去进行InnerClassSingleton类的实例化。
优势:避免了线程不安全,延迟加载,效率高。测试

7. 枚举
public enum EnumsSingleton {
    INSTANCE;

    @SuppressWarnings("unused")
    private void method() {
        System.out.println("------- newInstance");
    }

}

借助JDK1.5中添加的枚举来实现单例模式。不只能避免多线程同步问题,并且还能防止反序列化从新建立新的对象。多是由于枚举在JDK1.5中才添加、因此在实际项目开发中、不多见人这么写过。优化

到这单例几种实现方式以及每种方式的优缺点都作了一些简单的介绍、枚举虽小可是设计的知识点不少。

优势

  1. 在单例模式中,活动的单例只有一个实例,对单例类的全部实例化获得的都是相同的一个实例。这样就 防止其它对象对本身的实例化,确保全部的对象都访问一个实例
  2. 单例模式具备必定的伸缩性,类本身来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
  3. 提供了对惟一实例的受控访问。
  4. 因为在系统内存中只存在一个对象,所以能够 节约系统资源,当 须要频繁建立和销毁的对象时单例模式无疑能够提升系统的性能。
  5. 容许可变数目的实例。
  6. 避免对共享资源的多重占用

缺点

  1. 不适用于变化的对象,若是同一类型的对象老是要在不一样的用例场景发 生变化,单例就会引发数据的错误,不能保存彼此的状态。
  2. 因为单利模式中没有抽象层,所以单例类的扩展有很大的困难。
  3. 单例类的职责太重,在必定程度上违背了“单一职责原则”。
  4. 滥用单例将带来一些负面问题,如为了节省资源将数据库链接池对象设 计为的单例类,可能会致使共享链接池对象的程序过多而出现链接池溢 出;若是实例化的对象长时间不被利用,系统会认为是垃圾而被回收, 这将致使对象状态的丢失。

使用场景

  1. 须要频繁的进行建立和销毁的对象;
  2. 建立对象时耗时过多或耗费资源过多,但又常常用到的对象;
  3. 工具类对象;
  4. 频繁访问数据库或文件的对象。

注意

最后在简单聊一下如何防止暴力破坏单例。主要介绍两种方式以及如何来防范这两种方式。
1: 利用Java的反射方式

EagerSingleton instance = EagerSingleton.getInstance();
        Constructor instance2 = instance.getClass().getDeclaredConstructor();
         instance2.setAccessible(true);
         EagerSingleton instance3 = (EagerSingleton) instance2.newInstance();
         System.out.println("===" + instance);
         System.out.println("===" + instance3);

利用Java的反射方式能够达到爆力破解单例的效果、运行结果我就不在这贴出了有兴趣的能够本身试试instance 和 instance3 确定不是一个对象。
如何来防范这方式? 其实也很简单Java Security 中为咱们提供了现成的方法。只须要在私有构造中使用SecurityManager 进行检查下就能够代码以下。

// 私有的构造方法,防止外部实例化
     private EagerSingleton() {
         SecurityManager sm = new SecurityManager();
         sm.checkPermission(new ReflectPermission("禁止反射"));
     }

2: 第二种方式是利用Java 序列化和反序列化来实现

ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("1.txt"));
         out.writeObject(instance);
         out.close();
         ObjectInputStream in = new ObjectInputStream(new FileInputStream("1.txt"));
         EagerSingleton readObject = (EagerSingleton) in.readObject();
         in.close();
         System.out.println("==" + instance);
         System.out.println("==" + readObject);

如何防范? 很简单只须要重写readResolve() 反方就能够了

private Object readResolve() {
         return EagerSingleton.instance;
     }

两种暴力破解和防范的方式都介绍完了,感兴趣的同志能够去试试我这里没有贴出完整的测试代码和运行结果。

~~~~~~到这咱们的小单例已经介绍完了,有没有感到惊讶!!!

相关文章
相关标签/搜索