乐趣区

关于java:初学-Java-设计模式六实战单例模式

一、单例模式介绍

1. 解决的问题

  • 保障一个类只有一个实例。 最常见的是管制某些共享资源(例如数据库或文件)的拜访权限。

    运作形式是这样的:如果创立了一个对象,同时过一会儿后决定再创立一个新对象,此时会取得之前已创立的对象,而不是一个新对象。

    留神,一般构造函数无奈实现上述行为,因为构造函数的设计决定了它 必须 总是返回一个新对象。

  • 为该实例提供一个全局拜访节点。 还记得用过的那些存储重要对象的全局变量吗?它们在应用上非常不便,但同时也十分不平安,因为任何代码都有可能笼罩掉那些变量的内容,从而引发程序解体。

    和全局变量一样,单例模式也容许在程序的任何中央拜访特定对象。同时能够爱护该实例不被其余代码笼罩。

2. 定义

单例模式是一种创立型设计模式,可能保障一个类只有一个实例,并提供一个拜访该实例的全局节点。

3. 利用场景

  • 程序中的某个类对于所有客户端只有一个可用的实例,能够应用单例模式。
  • 要更加严格地管制全局变量,能够应用单例模式。

留神:能够随时调整限度并设定生成单例实例的数量,只需批改 获取实例 办法,即 getInstance 中的代码即可实现。

4. 与其余设计模式的关系

  • 形象工厂模式 生成器模式 原型模式 都能够用 单例 来实现。
  • 外观模式 类通常能够转换为 单例模式 类,因为在大部分状况下一个外观模式就足够了。
  • 如果能将对象的所有共享状态简化为一个享元对象,那么 享元模式 就和 单例模式 相似。但这个两个设计模式有两个根本性的不同。

    1. 只会有一个单例实体,但享元类能够有多个实体,各实体的外在状态也能够不同。
    2. 单例对象能够是可变的。享元对象是不可变的。

二、单例模式优缺点

1. 长处

  • 能够保障一个类只有一个实例。
  • 取得了一个指向该实例的全局拜访节点。
  • 仅在首次申请单例对象时对其进行初始化。

2. 毛病

  • 违反了繁多职责准则。
  • 单例模式可能覆盖不良设计,比方程序各组件之间相互了解过多等。
  • 该模式在多线程环境下须要进行非凡解决,防止多个线程多次出创立单例对象。
  • 单例的客户端代码单元测试可能会比拟艰难,因为许多测试框架以基于继承的形式创立模仿对象。因为单例类的构造函数是公有的,而且绝大部分语言无奈重写静态方法,所以须要认真思考模仿单例的办法。

三、7 种单例模式实现

动态类应用

/**
 * 动态类单例实现
 */
public class StaticSingleton {public static Map<String,String> hashMap = new ConcurrentHashMap<String, String>();
}
  • 这种形式在咱们的业务开发场景中十分常见,这样的形式能够在第一次运行的时候间接初始化 Map 类。
  • 在不须要维持任何状态,不须要提早加载,仅仅用于全局拜访时,这种形式更便于应用。
  • 但在须要被继承、须要维持一些特定状态时,单例模式更适宜。

单例模式的实现形式比拟多,次要是实现上是否反对懒汉模式、是否线程平安。

接下来通过不同的单例模式实现形式来解析单例模式。

1. 懒汉单例模式(线程不平安)

/**
 * 懒汉单例模式(线程不平安)*/
public class SlobThreadUnsafeSingleton {
    private static SlobThreadUnsafeSingleton instance;

    /**
     * 单例的构造函数必须永远是公有类型,以避免应用 `new` 运算符间接调用构造方法
     */
    private SlobThreadUnsafeSingleton() {}

    public static SlobThreadUnsafeSingleton getInstance() {if (ObjectUtils.isEmpty(instance)) {instance = new SlobThreadUnsafeSingleton();
        }
        return instance;
    }
}

这种懒汉单例模式满足了懒加载,但当多个访问者同时获取对象实例时(多过程),会导致多个实例并存,没有达到单例模式的需要。

2. 懒汉单例模式(线程平安)

/**
 * 懒汉单例模式(线程平安)*/
public class SlobThreadSafeSingleton {
    private static SlobThreadSafeSingleton instance;

    /**
     * 单例的构造函数必须永远是公有类型,以避免应用 `new` 运算符间接调用构造方法
     */
    private SlobThreadSafeSingleton() {}

    public static synchronized SlobThreadSafeSingleton getInstance() {if (ObjectUtils.isEmpty(instance)) {instance = new SlobThreadSafeSingleton();
        }
        return instance;
    }
}

这种懒汉单例模式是线程平安的,但因为锁在办法上,所有的拜访都须要锁占用,会导致资源的节约。非非凡状况,不倡议应用此种形式来实现单例模式。

3. 饿汉单例模式(线程平安)

/**
 * 饿汉单例模式(线程平安)*/
public class EagerSingleton {private static EagerSingleton instance = new EagerSingleton();

    /**
     * 单例的构造函数必须永远是公有类型,以避免应用 `new` 运算符间接调用构造方法
     */
    private EagerSingleton() {}

    private static EagerSingleton getInstance() {return instance;}
}

饿汉单例模式并不是懒加载,简略来说就是无论是否用到该类都会在程序启动之初创立。这种形式会导致的问题相似一关上某个软件,手机间接卡死(开启了过多无用性能,导致内存不足)。

4. 双重校验锁单例模式(线程平安)

/**
 * 双重校验锁单例模式(线程平安)*/
public class DoubleCheckingLockingSingleton {
    private static volatile DoubleCheckingLockingSingleton instance;

    /**
     * 单例的构造函数必须永远是公有类型,以避免应用 `new` 运算符间接调用构造方法
     */
    private DoubleCheckingLockingSingleton() {}

    public static DoubleCheckingLockingSingleton getInstance() {if (ObjectUtils.isNotEmpty(instance)) {return instance;}
        synchronized (DoubleCheckingLockingSingleton.class) {if (ObjectUtils.isEmpty(instance)) {instance = new DoubleCheckingLockingSingleton();
            }
        }
        return instance;
    }
}

双重校验锁形式实现的单例模式是办法级锁的优化,缩小了局部获取实例的耗时,同时也满足了懒加载。

5. 动态外部类单例模式(线程平安)

/**
 * 动态外部类单例模式(线程平安)*/
public class StaticInnerSingleton {
    private static class SingletonHolder {private static final StaticInnerSingleton INSTANCE = new StaticInnerSingleton();
    }

    /**
     * 单例的构造函数必须永远是公有类型,以避免应用 `new` 运算符间接调用构造方法
     */
    private StaticInnerSingleton(){}

    public static final StaticInnerSingleton getInstance() {return SingletonHolder.INSTANCE;}
}

动态外部类实现的单例模式,既保证了线程平安,也保障了懒加载,同时不会因为加锁的形式导致性能开销过大。

这次要是因为 JVM 虚拟机能够保障多过程并发拜访的正确性,即一个类的构造方法在多线程环境下,能够被正确加载。

因而,动态类外部类的实现形式十分举荐应用。

6. CAS「AtomicReference」单例模式(线程平安)

/**
 * CAS「AtomicReference」单例模式(线程平安)*/
public class CompareAndSwapSingleton {private static final AtomicReference<CompareAndSwapSingleton> INSTANCE = new AtomicReference<CompareAndSwapSingleton>();

    private static CompareAndSwapSingleton instance;

    /**
     * 单例的构造函数必须永远是公有类型,以避免应用 `new` 运算符间接调用构造方法
     */
    private CompareAndSwapSingleton() {}

    public static final CompareAndSwapSingleton getInstance() {for (; ;) {CompareAndSwapSingleton instance = INSTANCE.get();
            if (ObjectUtils.isNotEmpty(instance)) {return instance;}
            INSTANCE.compareAndSet(null, new CompareAndSwapSingleton());
            return INSTANCE.get();}
    }
}

Java 并发库提供了很多原子类来反对并发拜访的数据安全性:AtomicIntegerAtomicBoolean AtomicLongAtomicReference

AtomicReference 能够封装援用一个实例,反对并发拜访,该单例形式就是应用该特点实现。

采纳 CAS 形式的长处就是不须要传统的加锁形式来保障线程平安,而是依赖 CAS 的忙等算法,依赖底层硬件的实现,来保障线程平安。绝对于其余锁的实现没有线程的切换和阻塞,也就没有了额定开销,因而能够反对较大的并发。

当然,CAS 形式也存在毛病,忙等即如果始终没有获取到将会处于死循环中。

7. 枚举单例模式(线程平安)

/**
 * 枚举单例(线程平安)*/
public enum EnumSingleton {
    INSTANCE;

    public void whateverMethod() {System.out.println("enum singleton method");
    }
}

约书亚·布洛克(英语:Joshua J. Bloch,1961 年 8 ⽉ 28 ⽇-),美国驰名程序员,《Effective Java》作者。他为 Java 平台设计并实作了许多的性能,曾负责 Google 的⾸席 Java 架构师(Chief Java Architect)。

《Effective Java》作者约书亚·布洛克举荐应用枚举的形式解决单例模式,这种形式应该是平时起码用到的。

这种形式解决的单例模式的次要问题:线程平安、自在串行化、繁多实例。

调用形式

@Test
public void testEnumSingleton()
{EnumSingleton.INSTANCE.whateverMethod();
}

这种写法在性能上与共有域⽅法相近,然而它更简洁,⽆偿地提供了串⾏化机制,相对防⽌对此实例化,即便是在⾯对简单的串⾏化或者反射攻打的时候。尽管这种形式还没有⼴泛采⽤,然而单元素的枚举类型曾经成为实现单例 的最佳⽅法。

但这种⽅式在存在继承场景下是不可⽤的。

四、单例模式构造

  1. 单例(Singleton)类申明了一个名为 getInstance 获取实例的静态方法来返回其所属类的一个雷同实例。
  2. 单例的结构模式必须对客户端(Client)代码暗藏。调用 获取实例 办法必须是获取单例对象的惟一形式。

设计模式并不难学,其自身就是多年教训提炼出的开发指导思想,关键在于多加练习,带着应用设计模式的思维去优化代码,就能构建出更正当的代码。

源码地址:https://github.com/yiyufxst/d…

参考资料:
小博哥重学设计模式:https://github.com/fuzhengwei…
深刻设计模式:https://refactoringguru.cn/de…

退出移动版