单例模式确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供了全局访问的方法。git
单例模式的三个特色:github
单例模式的应用有,Windows 里的任务管理器(一个系统中只有一个)、网站计数器(实现多个页面的计数同步)、数据库链接池(减小资源损耗)等。数据库
根据单例模式的特色,咱们来实现单例模式,在类中提供一个静态方法来获取这个惟一的实例对象,给其余类提供实例,而且这个实例对象不能直接使用 new 建立,因此构造方法要声明成私有,这即是最简单的单例模式实现。这样的实如今单线程环境中,固然没问题,可是咱们还要考虑多线程环境下的安全实现。编程
下面是对单例模式的各类实现,而且对每种实现方法都在多线程环境中作了测试,全部代码都在个人 GitHub 仓库中中,传送门。该仓库还在完成中,用 Java 实现 23 种设计模式,并对设计原则和每种设计模式作出详细的分析,感兴趣的能够 fork 或者 star 哦,也欢迎小伙伴参与该仓库的完成。设计模式
经过 getInstance() 方法获得单例对象,单例对象在须要的时候才被延迟建立,因此称之为懒汉式。可是在多线程环境中,因为这个 getInstance() 方法可能被多个线程同时调用,这极可能会建立多个实例,因此这种实如今多线程环境下是不安全的。安全
给 getInstance() 加上 synchronized 关键字后,能够保证这个方法在同一时间只能被一个线程调用,多个线程调用这个方法要排队依次调用,这就保证了只会建立一个单例对象,在多线程环境下是安全的。微信
相比于上面的懒汉式,饿汉式在类加载的时候就会建立实例对象,在 getInstance() 方法直接返回建立好的对象,简单直接,在多线程环境下也是安全的。 多线程
针对于上面的线程安全的懒汉式加载,这种实现方式不是直接给方法加上 synchronized 关键字,而是在 getInstance() 方法作双重检查来解决线程不安全的问题。这种方式容许多个线程同时调用该方法,可是在方法中会进行两次检查,第一次检查实例是否已经存在,若是不存在才进入下面的同步代码块,线程安全的建立实例,若是实例真的不存在(避免这是有其余线程建立好了,再次建立新的实例)才会建立实例。这种方式理论上要比直接使用 synchronized 关键字性能要高,可是对于不一样虚拟机对 volatile 关键字的优化,优点并不明显。性能
建立一个静态内部类,来建立实例,和上面饿汉式相比,虽然都是直接 new 实例,可是这种方式在外部类加载时,静态内部类并不会被加载。只有在第一次调用 getInstance() 方法时,才会显式的加载静态内部类,建立实例,也是一种延迟(懒)建立方式。测试
枚举实现单例模式是 Java 大牛们比较推荐的,由于这种方式实现很是简单,而且这种方式可是大多数单例模式的实现并非这种方式。这种方式须要开发者对枚举有清晰的认识,这里也简单的回顾一下枚举的基本知识。
枚举是在 Java1.5 以后出现的,能够更加简单的定义常量,经过反编译,咱们能够发现枚举其实也是一个 Java 类,这个类继承自 Enum 接口,定义的枚举对象会被加上 static final 关键字,这就是咱们不用枚举时声明常量的方式,另外在 static 静态代码块中初始化枚举对象,枚举的构造方法被加上了 private 关键字,防止其余类建立新的枚举对象实例。虽然前面几种方式没法直接使用 new 建立新的实例,可是能够用反射来绕过 private 限制,而枚举却有自带的序列化机制、防止反射攻击形成屡次实例化、线程安全的优势,从这些地方咱们均可以看出使用枚举是实现单例模式的绝佳方式。
根据对资源加载时机的须要,来选择合适的单例模式实现方式,若是是懒加载方式,能够选择懒汉方式和双重校验锁方式;若是将资源加载的时间提早来达到使用时的快速体验,能够选择饿汉方式;若是涉及到序列化建立单例对象,能够选择枚举方式。
单例模式的优势是提供了对惟一实例的访问控制,能够节约系统资源,但缺点是单例类的职责太重,而且缺乏抽象层难以扩展,不太符合单一职责原则。
想看更多编程文章,欢迎关注下方的微信公众号哦。