设计模式之单例模式


一、单例模式

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

相关推荐
丶白泽14 分钟前
重修设计模式-结构型-组合模式
设计模式·组合模式
苹果酱056715 分钟前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
掐指一算乀缺钱35 分钟前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
晚睡早起₍˄·͈༝·͈˄*₎◞ ̑̑40 分钟前
苍穹外卖学习笔记(七)
java·windows·笔记·学习·mybatis
就这个java爽!1 小时前
JAVA网络编程【基于TCP和UDP协议】超详细!!!
java·开发语言·网络·tcp/ip·udp·eclipse·idea
一叶飘零_sweeeet1 小时前
为什么 Feign 要用 HTTP 而不是 RPC?
java·网络协议·http·spring cloud·rpc·feign
懒洋洋大魔王1 小时前
7.Java高级编程 多线程
java·开发语言·jvm
茶馆大橘1 小时前
【黑马点评】已解决java.lang.NullPointerException异常
java·开发语言
星辰@Sea1 小时前
服务注册中心对比及使用场景分析
java·云原生
马剑威(威哥爱编程)1 小时前
除了递归算法,要如何优化实现文件搜索功能
java·开发语言·算法·递归算法·威哥爱编程·memoization