单例模式, 简而言之就是在整个应用程序里面有且仅有一个实例, 在程序的任何时候, 任何地方获取到的该对象都是同一个对象. 单例模式解决了一个全局的类被频繁创建和销毁的, 或者每次创建或销毁都需要消耗大量 CPU 资源的对象的问题. 单例模式总的可以分为懒汉模式和饿汉模式, 顾名思义, 懒汉模式是一个非常懒的汉子, 只要你没有使用到它, 它就永远不会实例化. 饿汉模式的意思就是, 汉子非常饥渴, 只要在程序的编译阶段就给你分配内存, 创建好对象.
将懒汉模式和饿汉模式细分, 又可以分为:
1, 懒汉模式
2, 饿汉模式
3, 双检模式
4, 静态内部类模式
5, 枚举模式
不管是用哪一种方式实现的单例模式, 其创建流程基本都是一直的: 首先将构造方法声明为 private 的, 这样就防止直接 new 出一个新的对象. 第二, 声明一个私有的成员变量, 即单例对象. 第三步, 声明一个 public 的静态方法, 用于获取或创建单例对象, 外部想要获取该对象必须通过这个方法获取.
一, 懒汉模式 1-- 线程安全
- /**
- * 饿汉模式 1
- */
- public class HungrySingleton1 {
- private static HungrySingleton1 singleton = new HungrySingleton1();
- private HungrySingleton1(){}
- public static HungrySingleton1 getInstance() {
- return singleton;
- }
- }
这种懒汉模式的优点是实现非常简单. 缺点是并起到懒加载的效果, 如果项目没有使用到这个对象的就会造成资源的浪费.
二, 饿汉模式 1-- 线程不安全
- /**
- * 懒汉模式 1
- */
- public class LazySingleton1 {
- private static LazySingleton1 singleton;
- private LazySingleton1(){}
- public static LazySingleton1 getInstance() {
- if (singleton == null) {
- singleton = new LazySingleton1();
- }
- return singleton;
- }
- }
这种写法虽然实现了懒加载的效果, 但是严格意义上并不是单例模式, 因为在多线程的环境下有可能会创建出多个不同的对象, 至于为什么, 不懂的可以看一下我之间写的关于 Java 内存模型的文章. 这种写法只能应用于单线程的环境下, 局限性很大. 实际中强烈不建议使用这种方法.
三, 懒汉模式 2-- 线程安全
- /**
- * 懒汉模式 2
- */
- public class LazySingleton2 {
- private static LazySingleton2 singleton;
- private LazySingleton2(){}
- public static synchronized LazySingleton2 getInstance() {
- if (singleton == null) {
- singleton = new LazySingleton2();
- }
- return singleton;
- }
- }
这种写法咋看跟上面的方法一样, 这种写法在方法上添加了 synchronized 关键字, 这样就保证了每次只能有一个线程进入方法体中, 解决了懒汉模式 1 中出现的问题. 这种写法的优点是实现了懒加载的效果, 缺点是效率非常低, 当多个线程同时获取实例时, 有可能会造成线程阻塞的情况. 不推荐使用.
懒汉模式 3-- 线程不安全
- /**
- * 懒汉模式 3
- */
- public class LazySingleton3 {
- private static LazySingleton3 singleton;
- private LazySingleton3(){}
- public static LazySingleton3 getInstance() {
- if (singleton == null) {
- synchronized (LazySingleton3.class) {
- if (singleton == null) {
- singleton = new LazySingleton3();
- }
- }
- }
- return singleton;
- }
- }
这种写法进行了两次 singleton == null 的判断, 在实际的应用中当我们调用这个方法时, 其实 99% 的几率是实例就已经创建好了, 因此第一个 singleton == null 能过滤掉 99% 的调用, 不用将方法锁起来, 从而提高了效率. 这种方法的优点是实现懒加载的效果, 效率和很高. 缺点是代码设计仍然后缺陷, jvm 在为对象分配内存和赋值并不是一个原子操作, 即 singleton = new LazySingleton3() 这段代码在 jvm 中是由三个步骤实现的, 首先 jvm 会在堆中为对象分配一定的内存空间, 然后完成对象的初始化工作, 然后将内存地址指向到对象中. 但是, 我们知道, jvm 在编译的时候并不总是根据我们编写的代码的顺序来执行了, 而是根据 jvm 觉得最优的顺序执行 (这个过程就叫做指令重排序), 所以有可能在执行了步骤 1 后就执行了步骤 3, 这时候第二个线程进来的发现 singleton 并不为空, 因此就直接返回了该对象, 因此造成空指针异常.
四, 双重检查锁模式 --- 线程安全
- /**
- * 懒汉模式 4
- */
- public class LazySingleton4 {
- private volatile static LazySingleton4 singleton;
- private LazySingleton4(){}
- public static LazySingleton4 getInstance() {
- if (singleton == null) {
- synchronized (LazySingleton4.class) {
- if (singleton == null) {
- singleton = new LazySingleton4();
- }
- }
- }
- return singleton;
- }
- }
相较于上面的方式, 这种方式只是在成员变量中添加了 volatile 关键字, 解决了指令重排序的问题, 同时确保当前线程修改了这个变量时, 其他的线程能够及时读到最新的值. 这种方法缺点是写起来比较复杂, 要求程序员对 jvm 比较理解. 优点是既保证了线程安全, 同时也能够保证了比较高的效率.
五, 静态内部类模式 -- 线程安全
- /**
- * 懒汉模式 5
- */
- public class LazySingleton5 {
- private LazySingleton5(){}
- private static class Holder {
- private static final LazySingleton5 INSTANCE = new LazySingleton5();
- }
- public static LazySingleton5 getInstance() {
- return Holder.INSTANCE;
- }
- }
这种写法实现比较简单, 即实现了懒加载的效果, 同时也保证的多线程环境下的线程安全问题. 推荐使用这种方式.
六, 枚举模式 -- 线程安全
- /**
- * 懒汉模式 6
- */
- public enum LazySingleton6 {
- INSTANCE
- }
- // 使用方法
- public class Test {
- public static void main(String[] args) {
- LazySingleton6 instance = LazySingleton6.INSTANCE;
- LazySingleton6 instance1 = LazySingleton6.INSTANCE;
- System.out.println(instance == instance1);
- }
- }
推荐写法, 简单高效. 充分利用枚举类的特性, 只定义了一个实例, 且枚举类是天然支持多线程的.
来源: https://www.cnblogs.com/rainple/p/11886524.html