设计模式——单例模式

138 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

单例模式(Singleton Pattern)

定义

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

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

架构图解

单例模式的 UML 图

使用场景&解决的问题

当您想控制实例数目,节省系统资源的时候,避免一个全局使用的类频繁地创建与销毁。

例如log4j2中使用的单例模式

实现细节

其构造函数是私有的

优点&缺点

优点:

1.在内存中只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁对象的时候

2.避免对资源的多重占用(比如写文件操作)

缺点:没有接口, 不能继承,与单一原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化

代码实例

public class SingleObject {
    // 定义类的单实例
    private static SingleObject instance = new SingleObject();
    
    // 将构造函数变为私有
    private SingleObject() {
        
    }
    
    // 提供获取单例的方法
    public static SingleObject getInstance() {
        return instance;
    }
    
    
}

懒汉式

该方式是使用synchronized关键字进行加锁,保证了线程安全性。
优点:在第一次调用才初始化,避免了内存浪费。
缺点:对获取实例方法加锁,大大降低了并发效率。

由于加了锁,对性能影响较大,不推荐使用。

public class SingletonLazy {

    /**
     * 私有实例
     */
    private static SingletonLazy instance;

    /**
     * 私有构造方法
     */
    private SingletonLazy() {
    }

    /**
     * 唯一公开获取实例的方法(静态工厂方法),该方法使用synchronized加锁,来保证线程安全性
     *
     * @return
     */
    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }

}

饿汉式

饿汉式是利用类加载机制来避免了多线程的同步问题,所以是线程安全的。
优点:未加锁,执行效率高。
缺点:类加载时就初始化实例,造成内存浪费。

如果对内存要求不高的情况,还是比较推荐使用这种方式。

public class SingletonEager {

    /**
     * 私有实例,静态变量会在类加载的时候初始化,是线程安全的
     */
    private static final SingletonEager instance = new SingletonEager();

    /**
     * 私有构造方法
     */
    private SingletonEager() {
    }

    /**
     * 唯一公开获取实例的方法(静态工厂方法)
     *
     * @return
     */
    public static SingletonEager getInstance() {
        return instance;
    }
}

双重校验锁

利用了volatile修饰符的线程可见性(被一个线程修改后,其他线程立即可见),即保证了懒加载,又保证了高性能,所以推荐使用。

public class SingletonDCL {

    /**
     * 私有实例,volatile修饰的变量是具有可见性的(即被一个线程修改后,其他线程立即可见)
     */
    private volatile static SingletonDCL instance;

    /**
     * 私有构造方法
     */
    private SingletonDCL() {
    }

    /**
     * 唯一公开获取实例的方法(静态工厂方法)
     *
     * @return
     */
    public static SingletonDCL getInstance() {
        if (instance == null) {
            synchronized (SingletonDCL.class) {
                if (instance == null) {
                    instance = new SingletonDCL();
                }
            }
        }
        return instance;
    }
}

静态内部类

该模式利用了静态内部类延迟初始化的特性,来达到与双重校验锁方式一样的功能。由于需要借助辅助类,并不常用。

public class SingletonInnerClass {

    /**
     * 私有构造方法
     */
    private SingletonInnerClass() {
    }

    /**
     * 唯一公开获取实例的方法(静态工厂方法)
     *
     * @return
     */
    public static SingletonInnerClass getInstance() {
        return LazyHolder.INSTANCE;
    }

    /**
     * 私有静态内部类
     */
    private static class LazyHolder {
        private static final SingletonInnerClass INSTANCE = new SingletonInnerClass();
    }
}

枚举类

该方式利用了枚举类的特性,不仅能避免线程同步问题,还防止反序列化重新创建新的对象。这种方式是 Effective Java 作者 Josh Bloch 提倡的方式。

但由于这种编码方式还不能适应,所以实际工作中很少使用。

public enum SingletonEnum {

    INSTANCE;

    public void method() {
        System.out.println("枚举类中定义方法!");
    }

}

注意事项

在多线程场景下 getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。