单例模式

单例模式:一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式实现

普通饿汉模式

/**
 * 普通饿汉模式
 */
public class Singleton {
    /**
     * 类加载时进行实例化对象
     */
    private static final Singleton SINGLETON = new Singleton();
	
    /**
     * 私有构造,防止外部new对象
     */
    private Singleton() {
    }

    /**
     * 通过静态方法获取对象实例
     *
     * @return
     */
    public static Singleton getInstance() {
        return SINGLETON;
    }

    public void test() {
        System.out.println("普通饿汉模式");
    }
}

调用方式

Singleton singleton = Singleton.getInstance();
singleton.test();

优缺点:
优点: 类加载时就进行实例化,之后的操作效率会很高。
缺点: 由于类加载时就进行实例化,如果后续不对此类进行任何操作,就会导致内存的浪费。

线程不安全的懒汉模式

/**
 * 懒汉式(线程不安全)
 */
public class SingletonTwo {

    private static SingletonTwo instance;

    /**
     * 私有构造,防止外部new对象
     */
    private SingletonTwo() {

    }

    /**
     * 通过静态方法获取对象实例
     *
     * @return
     */
    public static SingletonTwo getInstance() {
        if(instance == null) {
            instance = new SingletonTwo();
        }
        return instance;
    }

    public void test() {
        System.out.println("懒汉式(线程不安全)");
    }
}

调用方式

SingletonTwo singleton = SingletonTwo.getInstance();
singleton.test();

优缺点:
优点: 在第一次调用的时候才进行实例化。
缺点: 当多个线程同时进入到 if(instance == null) {...} 时,会创建多个对象。

同步锁懒汉式

/**
 * 同步锁懒汉式(线程安全,效率低)
 */
public class SingletonThree {

    private static SingletonThree instance;

    /**
     * 私有构造,防止外部new对象
     */
    private SingletonThree() {

    }

    /**
     * 通过静态方法获取对象实例
     *
     * @return
     */
    public static synchronized SingletonThree getInstance() {
        if(instance == null) {
            instance = new SingletonThree();
        }
        return instance;
    }

    public void test() {
        System.out.println("同步锁懒汉式(线程安全,效率低)");
    }
}

调用方式:

SingletonThree singleton = SingletonThree.getInstance();
singleton.test();

优缺点:
优点: 在第一次调用的时候才进行实例化,且线程安全。
缺点: 使用 synchronized 的方式对方法加锁,会影响效率。

双重校验锁懒汉式

/**
 * 双重校验锁懒汉式(线程安全,且多线程环境下可以保持高性能)
 */
public class SingletonFour {

    /**
     * volatile是为了防止指令重排序
     */
    private static volatile SingletonFour instance;

    /**
     * 私有构造,防止外部new对象
     */
    private SingletonFour() {

    }

    /**
     * 通过静态方法获取对象实例
     *
     * @return
     */
    public static SingletonFour getInstance() {
        if(instance == null) {
            synchronized (SingletonFour.class) {
                if(instance == null) {
                    instance = new SingletonFour();
                }
            }
        }
        return instance;
    }

    public void test() {
        System.out.println("双重校验锁懒汉式(线程安全,且多线程环境下可以保持高性能)");
    }
}

调用方式:

SingletonFour singleton = SingletonFour.getInstance();
singleton.test();

优缺点
优点: 在第一次调用的时候才进行实例化,且线程安全,效率较高。
缺点: 实现复杂,且 volatile 需要在JDK1.5之后的版本才能确保安全。

当没有volatile关键字的时候是线程不安全的。

public class SingletonExample4 {

    private SingletonExample4(){}

    private static SingletonExample4 instance = null;

    //线程不安全
    //当执行instance = new SingletonExample4();这行代码时,CPU会执行如下指令:
    //1.memory = allocate() 分配对象的内存空间
    //2.ctorInstance() 初始化对象
    //3.instance = memory 设置instance指向刚分配的内存
    //单纯执行以上三步没啥问题,但是在多线程情况下,可能会发生指令重排序。
    // 指令重排序对单线程没有影响,单线程下CPU可以按照顺序执行以上三个步骤,但是在多线程下,如果发生了指令重排序,则会打乱上面的三个步骤。

    //如果发生了JVM和CPU优化,发生重排序时,可能会按照下面的顺序执行:
    //1.memory = allocate() 分配对象的内存空间
    //3.instance = memory 设置instance指向刚分配的内存
    //2.ctorInstance() 初始化对象


    //假设目前有两个线程A和B同时执行getInstance()方法,A线程执行到instance = new SingletonExample4(); B线程刚执行到第一个 if (instance == null){处,
    //如果按照1.3.2的顺序,假设线程A执行到3.instance = memory 设置instance指向刚分配的内存,此时,线程B判断instance已经有值,就会直接return instance;
    //而实际上,线程A还未执行2.ctorInstance() 初始化对象,也就是说线程B拿到的instance对象还未进行初始化,这个未初始化的instance对象一旦被线程B使用,就会出现问题。


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

静态内部类懒汉式

/**
 * 静态内部类懒汉式
 */
public class SingletonFive {

    /**
     * 私有构造,防止外部new对象
     */
    private SingletonFive() {

    }

    /**
     * 通过静态方法获取对象实例
     *
     * @return
     */
    public static SingletonFive getInstance() {
        return Singleton.SINGLETON;
    }

    public void test() {
        System.out.println("静态内部类懒汉式");
    }

    /**
     * 静态内部类实例化对象
     */
    private static class Singleton {
        /**
         * 类加载时进行实例化对象
         */
        private static final SingletonFive SINGLETON = new SingletonFive();
    }
}

调用方式:

SingletonFive singleton = SingletonFive.getInstance();
singleton.test();

优缺点
优点: 只有在调用 getInstance() 方法的时候,静态内部类才会被加载,从而对主类(我们需要的类)进行实例化,即线程安全,又效率高。
缺点: 多创建一个类。

枚举类饿汉式(防止反序列化)

/**
 * 枚举类饿汉式(防止反序列化)
 */
public class SingletonSix {

    private SingletonSix(){}

    public static SingletonSix getInstance(){
        return Singleton.INSTANCE.getInstance();
    }

    private enum Singleton{
        INSTANCE;
        private SingletonSix singleton;

        //JVM保证这个方法绝对只调用一次
        Singleton(){
            singleton = new SingletonSix();
        }
        public SingletonSix getInstance(){
            return singleton;
        }
    }
	
	public void test() {
        System.out.println("枚举类饿汉式(防止反序列化)");
    }
}

调用方式:

SingletonSix singleton = SingletonSix.getInstance();
singleton.test();

优缺点
优点: 实现简单,防止反序列化生成多个实例,且线程安全。
缺点: Enum 需在JDK1.5之后版本使用。

单例模式的优缺点

  • 优点
    单例模式在内存中只有一个实例,减少了内存开支,尤其是频繁的创建和销毁实例。
    由于只生成一个实例,所以减少了系统的性能开销。
    避免对资源的多重占用,例如写文件操作。
    单例模式可以在系统设置全局的访问点,优化和共享资源访问。
  • 缺点
    单例模式不易扩展,若要扩展,除了修改代码外别无他法。
    单例模式对测试不利。
    单例模式与单一职责原则有冲突,一个类应该只实现一个逻辑,而不用关心它是否是单例的。
posted @   fchhk  阅读(9)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示