设计模式(创建型模式)单例模式

目录

    • 一、单例模式的定义
    • 二、单例模式的实现
      • 2.1、饿汉式
      • 2.2、懒汉式
      • 2.3、双重检测
      • 2.4、静态内部类
      • 2.5、枚举
    • 三、优点和缺点

一、单例模式的定义

  如果一个类只允许创建一个对象(或实例),那么这个类就是一个单例类,这种设计模式就称为单例设计模式 ( Singleton Design Patter ),简称单例模式 ( Singleton Pattern ),从业务概念方面来讲,如果菜个类包含的数据在系统中只应保存一份,那么这个类就应该被设计为单例类。例如唯一递增工D 生成器类,如果程序中有两个ID生成器对象,那么有可能生成重复。

单例模式要注意的几个点

  1. 构造函数必须具有 private 访问权限,这样才能避免通过关键字 new 创建实例
  2. 对象创建时的线程安全问题
  3. 是否支持延迟加载
  4. getInstance() 函数的性能是否足够高

二、单例模式的实现

2.1、饿汉式

  “饿汉”式的实现比我简单。在加载类时,实例就己经被创建并初始化,因此实例的创建过程是线程安全的。不过,这种实现方式不支持延迟加载,实例是提前创建好的,而非在使用时才创建。因此,这种实现方式被称为 “饿汉” 式。具体的代码实现如下所示:

HungryIdGenerator.java

package com.alian.singleton;

import java.util.concurrent.atomic.AtomicLong;

public class HungryIdGenerator {

    private AtomicLong id = new AtomicLong(0);

    private static final HungryIdGenerator instance = new HungryIdGenerator();

    private HungryIdGenerator() {}

    public static HungryIdGenerator getInstance() {
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }

    // HungryIdGenerator 类使用举例
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Long id = HungryIdGenerator.getInstance().getId();
            System.out.println(id);
        }
    }

}

2.2、懒汉式

  与 “饿汉” 式相反的就有 “懒汉” 式,相比 “饿汉” “懒汉” 式支持延迟加载,实例的创建和初始化推迟到真正使用时才进行。具体代码如下:

LazyIdGenerator.java

package com.alian.singleton;

import java.util.concurrent.atomic.AtomicLong;

public class LazyIdGenerator {

    private AtomicLong id = new AtomicLong(0);

    private static LazyIdGenerator instance;

    private LazyIdGenerator() {
    }

    public static synchronized LazyIdGenerator getInstance() {
        if (instance == null) {
            instance = new LazyIdGenerator();
        }
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }

	// LazyIdGenerator 类使用举例
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Long id = LazyIdGenerator.getInstance().getId();
            System.out.println(id);
        }
    }

}

   “饿汉式” ,提前创建和初始化实例,如果占用的系统资源很多,就导致启动时间变长,甚至若系统资源不够,系统可能就OOM了,不过也是一个好事,也能提前发现问题;但是只要系统启动,那么后续对请求的处理都会很快。
   “懒汉式” ,提供服务的时候创建和初始化实例,虽然加快系统启动,但是一旦服务器请求并发高,上面使用了 synchronized 可能会出现性能瓶颈。

2.3、双重检测

   “饿汉式” 不支持延迟加载; “懒汉式” 不支持高并发。我介绍一种既支持懒加载,又支持高并发的单例模式的实现方式:双重检测

DCLIdGenerator.java

package com.alian.singleton;

import java.util.concurrent.atomic.AtomicLong;

public class DCLIdGenerator {

    private AtomicLong id = new AtomicLong(0);

    private static volatile DCLIdGenerator instance;

    private DCLIdGenerator() {
    }

    public static DCLIdGenerator getInstance() {
        if (instance == null) {
            synchronized (DCLIdGenerator.class){
                if (instance == null) {
                    instance = new DCLIdGenerator();
                }
            }
        }
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }

    // DCLIdGenerator 类使用举例
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Long id = DCLIdGenerator.getInstance().getId();
            System.out.println(id);
        }
    }

}

  CPU指令重排导致对象被关键字new创建并赋值给instance之后,还没有来得初始化(比如构造方法的代码逻辑),就被另外一个线程使用到未完全初始化的对象。所以我们给instance加了关键字:volatile

2.4、静态内部类

  使用静态内部类实现的单例模式比双重检测更加的简单,静态内部类类似 “饿汉式” ,但是又能做到延迟加载。具体代码实现如下:

InnerClassIdGenerator.java

package com.alian.singleton;

import java.util.concurrent.atomic.AtomicLong;

public class InnerClassIdGenerator {

    private AtomicLong id = new AtomicLong(0);

    private static InnerClassIdGenerator instance;

    private InnerClassIdGenerator() {
    }

    private static class SingletonHolder {
        private static final InnerClassIdGenerator instance = new InnerClassIdGenerator();
    }


    public static InnerClassIdGenerator getInstance() {
        return SingletonHolder.instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }

    // InnerClassIdGenerator 类使用举例
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Long id = InnerClassIdGenerator.getInstance().getId();
            System.out.println(id);
        }
    }

}

  SingletonHolder是一个静态内部类,当外部类InnerClassIdGenerator加载时不会加载SingletonHolder,只有当方法getInstance()被调用时,SingletonHolder才会加载,才会创建实例instanceinstance。而instance的唯一性和创建过程的线程安全都是由JVM保证的。所以说这种方式既实现了懒加载,又保证了线程的安全。

2.5、枚举

  基于枚举的单例模式的实现方式,通过Java枚举类型本身的特性,保证了实例创建的线程安全和实例的唯一性。具体代码实现如下所示:

EnumIdGenerator.java

package com.alian.singleton;

import java.util.concurrent.atomic.AtomicLong;

public enum EnumIdGenerator {

    instance;

    private AtomicLong id = new AtomicLong(0);

    public long getId() {
        return id.incrementAndGet();
    }

    // EnumIdGenerator 类使用举例
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Long id = EnumIdGenerator.instance.getId();
            System.out.println(id);
        }
    }

}

三、优点和缺点

  当涉及到单例模式的实现时,不同的方法有其独特的优点和缺点:

  1. 饿汉式(Eager Initialization):

优点:

  • 实现简单,线程安全。
  • 在类加载时就创建实例,避免了多线程同步问题。

缺点:

  • 如果实例未被使用,可能会造成资源浪费。
  • 不支持延迟加载,在初始化时就创建实例。
  1. 懒汉式(Lazy Initialization):

优点:

  • 延迟加载,只有在第一次使用时才会创建实例。
  • 节省了资源,避免了饿汉式的资源浪费。

缺点:

  • 非线程安全,在多线程环境下需要额外处理,否则可能会创建多个实例。
  • 需要考虑线程安全,可以使用同步方法或双重检查锁定(Double-Checked Locking)来解决。
  1. 双重检查(Double-Checked Locking):

优点:

  • 实现延迟加载,同时保证了线程安全。

缺点:

  • 实现稍微复杂,需要考虑内存模型。
  1. 静态内部类(Static Inner Class):

优点:

  • 延迟加载,且线程安全。
  • 利用 Java 类加载机制,保证只有一个实例。

缺点:

  • 实现较为复杂。
  1. 枚举(Enum)方式:

优点:

  • 线程安全。
  • 实现简单,枚举本身就是单例模式的实现方式,保证只有一个实例。
  • 避免了反序列化重新创建新的对象。

缺点:

  • 不能延迟加载,实例在枚举类被加载时就会创建。

  在选择单例模式的实现方式时,需要根据具体需求权衡其优缺点。如果需要保证线程安全,最好选择双重检查锁定、静态内部类或枚举实现。如果对性能要求不高或者可以提前创建实例,饿汉式是一个简单且可靠的选择。如果需要延迟加载,并能确保线程安全,懒汉式也是一个不错的选择。

你可能感兴趣的:(设计模式,设计模式,单例模式)