• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

初学 Java 设计模式(六):实战单例模式

互联网 diligentman 2周前 (02-17) 12次浏览

一、单例模式介绍

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();
}

这种写法在功能上与共有域⽅法相近,但是它更简洁,⽆偿地提供了串⾏化机制,绝对防⽌对此实例化,即使是在⾯对复杂的串⾏化或者反射攻击的时候。虽然这种方式还没有⼴泛采⽤,但是单元素的枚举类型已经成为实现单例 的最佳⽅法。

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

四、单例模式结构

初学 Java 设计模式(六):实战单例模式

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

设计模式并不难学,其本身就是多年经验提炼出的开发指导思想,关键在于多加练习,带着使用设计模式的思想去优化代码,就能构建出更合理的代码。

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

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


程序员灯塔
转载请注明原文链接:初学 Java 设计模式(六):实战单例模式
喜欢 (0)