大话设计模式笔记(十八)の单例模式
单例模式
定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以创建,并且它可以提供一个访问该实例的方法。
uml图
方式一:单线程下的单例
/** * created by callmedevil on 2019/8/17. */ public class singleton { private static singleton instance; private singleton(){} //构造方法私有,防止外界创建实例 // 获得本类实例的唯一全局访问点 public static singleton getinstance(){ if (instance == null) { //若实例不存在,则创建一个新实例,否则直接返回已有实例 instance = new singleton(); } return instance; } }
测试
public class test { public static void main(string[] args) { singleton s1 = singleton.getinstance(); singleton s2 = singleton.getinstance(); if (s1 == s2) { system.out.println("两个对象是相同的实例"); } } }
测试结果
两个对象是相同的实例
在没有并发问题的情况下,这种方式也是使用比较多的。但缺点也很明显,多线程下根本没法用。
方式二:多线程下的单例
/** * created by callmedevil on 2019/8/17. */ public class singletononlock { private static singletononlock instance; private singletononlock(){} public static singletononlock getinstance(){ // 同步代码块,只有一个线程能进入,其他阻塞 synchronized (singletononlock.class){ if(instance == null){ instance = new singletononlock(); } } return instance; } }
存在问题
当存在对象实例时,完全不用担心并发时导致堆中创建多个实例,但每次调用 getinstance() 方法时都被加锁,是会影响性能的,因此这个类可以继续改良。
方式三:双重锁定(dcl)
/** * created by callmedevil on 2019/8/17. */ public class singletonondoublechecklock { private static singletonondoublechecklock instance; private singletonondoublechecklock(){} public static singletonondoublechecklock getinstance(){ // 先判断实例是否存在,不存在再考虑并发问题 if (instance == null) { synchronized (singletonondoublechecklock.class){ if(instance == null){ instance = new singletonondoublechecklock(); } } } return instance; } }
两次判断实例是否存在的原因
当实例存在时,就直接返回,这是没有问题的。当实例为空并且有两个线程调用 getinstance() 方法时,它们都可以通过第一重 instace == null 的判断,然后由于 synchronized 机制,只有一个线程可以进入,另一个阻塞,必须要在同步代码块中的线程出来后,另一个线程才会进入。而此时如果没有第二重的判断,那第二个线程仍然会创建实例,这就达不到单例的目的了。
但这种方式是最让人“诟病”的一种不推荐方式,技巧看上去很好,但实际上同样影响性能。
方式四:静态初始化
/** * 该类声明为final ,阻止派生,因为派生可能会增加实例 * created by callmedevil on 2019/8/17. */ public final class singletonstatic { // 第一次引用类的任何成员时就创建好实例,同时没有并发问题 private static final singletonstatic instance = new singletonstatic(); private singletonstatic(){} public static singletonstatic getinstance(){ return instance; } }
jvm第一次加载类的时候就已经创建好了实例,如果接下来的很长时间都没有用到的话,占用的内存相当于被浪费了,也不是最让人推荐的一种方式。当然现在的服务器容量也越来越大,单单一个实例的内存也并不是任何情况都要考虑节省。除非追求极致。。
总结
- 静态初始化的方式是自己被加载时就已经将自己实例化,因此也被称为“饿汉式”。
- 其他方式是要在第一次被引用时,才会将自己实例化,所以被称为“懒汉式”。
- 其实单例模式还有很多种实现方式,下面再提一种《大话设计模式》中未提到的实现:静态内部类。 这是楼主在《java并发编程实战》中看到的,也是最为推荐的一种。
扩展
方式五:静态内部类
/** * created by callmedevil on 2019/8/17. */ public class singletonstaticclass { private singletonstaticclass() {} public singletonstaticclass getinstande() { return interclass.instance; } // 静态内部类,没有并发问题 private static final class interclass { public static singletonstaticclass instance = new singletonstaticclass(); } }
推荐原因
jvm第一次加载外部的 singletonstaticclass 时,并不会直接实例化,所以这种方式也属于“懒汉式”。只有在第一次调用 getinstance() 方法时,jvm才会加载内部类 interclass,接着才实例化静态变量,也就是我们需要的外部类的单例。这样不仅延时了实例化,同时也解决了并发访问的问题,因此该方式是最为推荐的一种方式。
上一篇: ES6常用的新特性
下一篇: 【Python爬虫】爬取百度翻译的结果