设计模式之单例模式


一、单例模式

单例模式属于创建型模式, 是 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 效果时,才会使用 登记方式。如果涉及到反序列化创建对象时,可以尝试使用 枚举方式。如果有其他特殊的需求,可以考虑使用 双检锁方式。

相关推荐
渣哥13 小时前
原来 Java 里线程安全集合有这么多种
java
间彧13 小时前
Spring Boot集成Spring Security完整指南
java
间彧14 小时前
Spring Secutiy基本原理及工作流程
java
数据智能老司机14 小时前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
Java水解15 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
数据智能老司机15 小时前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
洛小豆17 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学17 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole17 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊17 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端