单例模式是一种经常使用的设计模式、也多是设计模式中代码量最少的设计模式。可是少并不意味着简单、想要用好、用对单例、就的费一番脑子了。由于它里面涉及到了不少Java底层的知识如类装载机制、Java内存模型、volatile等知识点。数据库
单例模式属于23中设计模式中的建立型模式、定义是确保某一个类只有一个实例、并提供一个全局的访问点。设计模式
具备如下3个特性:
单例要求类只能返回同一对象的引用、必须提供一个静态获取该实例的方法
实现能够经过如下两步:安全
public class EagetSingleton { private static final EagetSingleton INSANCE = new EagetSingleton(); // 私有化构造函数、防止外部实例化 private EagetSingleton() { } // 提供静态外部访问方法 public static EagetSingleton getInstance() { return INSANCE; } }
优势:写法简单、类装载时就实例化了静态变量、避免了线程并发问题。
缺点:在类装载过程当中就实例化了对象、形成了资源浪费。多线程
public class StaticBlockSingleton { private static StaticBlockSingleton INSTANCE = null; static { try { INSTANCE = new StaticBlockSingleton(); } catch (Exception e) { } } // 私有化构造函数、防止外部实例化 private StaticBlockSingleton() { } // 提供静态外部访问方法 public static StaticBlockSingleton getInstance() { return INSTANCE; } }
这种方式和上述实现方式基本相同、只是把类实例化的过程放到了静态代码块中来实例化、一样也是在类装载过程执行静态代码块、优缺点基本相同可是它能够在类实例化过程当中作一些额外的操做如异常处理等。并发
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 处代码、破坏了单例。函数
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() 都须要同步访问、这种状况多线程并发效率很是低下、其实咱们只须要在对象还没实例化前加锁就能够了、实例化后就不存在并发问题了。工具
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的一些执行优化、指令重排等性能
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类的实例化。
优势:避免了线程不安全,延迟加载,效率高。测试
public enum EnumsSingleton { INSTANCE; @SuppressWarnings("unused") private void method() { System.out.println("------- newInstance"); } }
借助JDK1.5中添加的枚举来实现单例模式。不只能避免多线程同步问题,并且还能防止反序列化从新建立新的对象。多是由于枚举在JDK1.5中才添加、因此在实际项目开发中、不多见人这么写过。优化
到这单例几种实现方式以及每种方式的优缺点都作了一些简单的介绍、枚举虽小可是设计的知识点不少。
最后在简单聊一下如何防止暴力破坏单例。主要介绍两种方式以及如何来防范这两种方式。
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; }
两种暴力破解和防范的方式都介绍完了,感兴趣的同志能够去试试我这里没有贴出完整的测试代码和运行结果。
~~~~~~到这咱们的小单例已经介绍完了,有没有感到惊讶!!!