设计模式之单例模式


一、单例模式

单例模式属于创建型模式, 是 Java 中最简单的设计模式之一, 它提供了一种创建对象的最佳方式,

这种模式涉及到一个单一的类, 该类负责创建自己的对象, 同事确保只有单个对象被创建, 提供唯一对象的创建方式, 可以直接访问, 不需要实例化该类的对象

注意:

  1. 单例类智能有一个实例
  2. 单例类必须自己创建自己的唯一实例
  3. 单例类笔记给其他所有对象提供这一实例

二、适用场景

  1. 需要生成唯一序列的环境
  2. 需要频繁实例化然后销毁的对象
  3. 创建对象时耗时过多或者耗资源过多, 但是有经常用到的对象
  4. 方便资源互相通信的环境

三、单例模式的优缺点

优点:

  1. 在内存中只有一个对象, 节省内存空间;
  2. 避免频繁的创建销毁对象, 可以提高性能;
  3. 避免对共享资源的多重占用, 简化访问;
  4. 为整个系统提供一个全局访问点.

缺点:

  1. 不适用于变化频繁的对象;
  2. 滥用单例模式带来的一些问题, 如为了节省资源将数据库连接池对象设计为单例, 可能会导致共享连接池对象的程序过多而出现连接池溢出;
  3. 如果实例化的对象长时间不被利用, 系统会认为该独享是垃圾而被回收, 可能导致对象状态的丢失;

四、单例模式的分类

  1. 懒汉式, 线程不安全
  2. 懒汉式, 线程安全
  3. 饿汉式
  4. 双检锁/双重校验锁
  5. 静态内部类
  6. 枚举

五、懒汉式, 线程不安全的

这种方式是最基本的实现方式, 因为没有加锁(synchronized), 所以这种方式是不安全、不支持多线程的, 且方式会懒加载很明显

java 复制代码
public class Singleton1 {
    private static Singleton1 instance;

    private Singleton1() {
    }

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

    public static void main(String[] args) {
        System.out.println(Singleton1.getInstance());
        System.out.println(Singleton1.getInstance());
        System.out.println(Singleton1.getInstance());
    }
}

输出:

text 复制代码
com.kino.designpattern.single.Singleton1@4554617c
com.kino.designpattern.single.Singleton1@4554617c
com.kino.designpattern.single.Singleton1@4554617c

六、懒汉式, 线程安全

像不安全的懒汉式一样, 这种方式也具有很好的懒加载, 适用于多线程, 但是效率很低

优点: 第一次调用才会初始化, 避免内存浪费

缺点: 必须加锁才能保证单例, 加锁会影响性能

java 复制代码
public class Singleton2 {
    private static Singleton2 singleton2;

    private Singleton2() {
    }

    public synchronized static Singleton2 getSingleton2() {
        if (singleton2 == null) {
            singleton2 = new Singleton2();
        }
        return singleton2;
    }

    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable, "singleton2 thread");
        thread.start();
    }
}

输出:

text 复制代码
com.kino.designpattern.single.Singleton2@152ee4dc
com.kino.designpattern.single.Singleton2@152ee4dc
com.kino.designpattern.single.Singleton2@152ee4dc
com.kino.designpattern.single.Singleton2@152ee4dc

七、饿汉式

这种方式比较常用, 但是容易产生垃圾对象, 饿汉式是线程安全的, 不是懒加载的

优点: 没有加锁, 执行效率会高

缺点: 类加载的时候就初始化, 浪费内存

懒汉式基于 classloader 机制避免了多线程的同步问题, 不过, instance 在类加载的时候就实例化了

java 复制代码
public class Singleton3 {
    private static Singleton3 singleton3 = new Singleton3();

    private Singleton3() {
    }

    public static Singleton3 getSingleton3() {
        return singleton3;
    }
}

八、双检锁/双重校验锁

要求 jdk 必须大于 1.5, 这种方式是懒加载 且 线程安全的

这种方式采用双锁机制, 安全且在多线程的情况下能保持高性能

java 复制代码
public class Singleton4 {
    private volatile static Singleton4 singleton4;

    private Singleton4() {}

    public static Singleton4 getSingleton4() {
        if(singleton4 == null){
            synchronized (Singleton4.class) {
                if (singleton4 == null) {
                    singleton4 = new Singleton4();
                }
            }
        }
        return singleton4;
    }
}

九、登记式/静态内部类

这种方式不仅是懒加载的, 而且是线程安全的, 这种方式能达到双检锁方式一样的效果, 但是实现起来更简单。

这种方式只适用于静态域的情况, 双检锁的方式可以在实例域需要延迟初始化时使用。

这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 饿汉式式 不同的是:第 饿汉式式 只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

java 复制代码
public class Singleton5 {
    private Singleton5() {
    }

    private static class SingletonHolder {
        static final Singleton5 INSTANCE = new Singleton5();
    }

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

    public static void main(String[] args) {
        Singleton5Runnable singleton5Runnable = new Singleton5Runnable();
        new Thread(singleton5Runnable).start();
    }
}

输出:

text 复制代码
com.kino.designpattern.single.Singleton5@1873e6a0
com.kino.designpattern.single.Singleton5@1873e6a0
com.kino.designpattern.single.Singleton5@1873e6a0

十、枚举

要求 jdk1.5 及以上, 不是懒加载的, 是多线程安全的

这种方式没有被广泛采用, 但是这是实现单例模式的最佳方式, 它更简洁, 这种方式是 《Effective Java》作者Josh Bloch 提倡的方式, 它不仅仅能够避免多线程同步问题, 而且还自动支持序列机制, 防止反序列化重新创建的对象, 绝对防止多次实例化

java 复制代码
public enum Singleton6 {
    instance;
    private EnumResource enumResource;

    private Singleton6() {
        enumResource = new EnumResource();
    }

    public EnumResource getInstance() {
        return enumResource;
    }
}

class EnumResource {
    public static void main(String[] args) {
        System.out.println(Singleton6.instance.getInstance());
        System.out.println(Singleton6.instance.getInstance());
        System.out.println(Singleton6.instance.getInstance());
    }
}

输出:

text 复制代码
com.kino.designpattern.single.EnumResource@4554617c
com.kino.designpattern.single.EnumResource@4554617c
com.kino.designpattern.single.EnumResource@4554617c

十一、总结

一般情况下,不建议使用 懒汉方式, 建议使用 饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用 登记方式。如果涉及到反序列化创建对象时,可以尝试使用 枚举方式。如果有其他特殊的需求,可以考虑使用 双检锁方式。

相关推荐
Yyyyyy~4 分钟前
【java】数组的定义和使用
java
麻辣布丁4 分钟前
java集合篇面试模拟
java·开发语言·面试
niucloud-admin6 分钟前
JAVA V6 多商户商城 开发文档——插件开发规范
java·开发语言
嘟哩DuliDuli19 分钟前
创作 Agent 的任务状态该如何保存
android·java·javascript
善良勤劳勇敢而又聪明的老杨30 分钟前
【AI编程系列】MCP 常用设计模式解读
设计模式·ai编程
wdfk_prog31 分钟前
lely-canopen-rtt:为什么推荐在 RT-Thread 上使用 Lely CANopen 构建主站
java·运维·缓存·docker·容器
Token掘金室37 分钟前
JSON模式结构化输出报错
java·服务器·json
秋饼42 分钟前
Spring AI 2.0 接入 DeepSeek V4.1 Flash 生产级实战
java·ai·技术分享·后端开发
IT枫斗者枫哥43 分钟前
Java 分批导出仍然 OOM?用 32 MiB 堆复现三种 CSV 写法
java
shehuiyuelaiyuehao1 小时前
算法43,外观数列,模拟算法+双指针
java·算法