【JavaEE】多线程(4) -- 单例模式

发布时间:2023年12月17日

目录

什么是设计模式?

1.饿汉模式

2.懒汉模式

线程安全问题


什么是设计模式?

设计模式好?象棋中的 "棋谱". 红?当头炮, ???来跳. 针对红?的?些?法, ??应招的时候有? 些固定的套路. 按照套路来?局势就不会吃亏.

软件开发中也有很多常?的 "问题场景". 针对这些问题场景, ?佬们总结出了?些固定的套路. 按照这 个套路来实现代码, 也不会吃亏.

单例模式能保证某个类在程序中只存在唯??份实例, ?不会创建出多个实例. 这?点在很多场景上都需要. ?如 JDBC 中的 DataSource 实例就只需要?个.?

单例模式具体的实现?式有很多. 最常?的是 "饿汉" 和 "懒汉" 两种.

1.饿汉模式

所谓 "饿" 形容 "非常迫切" , 实例实在类加载的时候就创建了, 创建时机非常早, 相当于程序一启动?, 实例就创建了, 就使用 "饿汉" 形容创建实例非常早.?

class Singleton {
    private static Singleton instance = new Singleton();
    
    public static Singleton getInstance() {
        return instance;
    }
    
    private Singleton(){}; //阻止后续代码new出新的实例
}

2.懒汉模式

类加载的时候不创建实例. 第?次使?的时候才创建实例.

// 懒汉模式实现单例模式
class SingletonLazy {
    // 这个引用指向唯一的实例, 先初始化为null, 而不是立即创建实例
    private static SingletonLazy instance = null;

    public SingletonLazy getInstance() {
        if(instance == null) {
            instance = new SingletonLazy();
        }

        return instance;
    }

    private SingletonLazy() {};
}

如果是首次调用 getInstance , 那么此时 instance 引用为 null, 就会进入 if 条件, 从而把实例创建出来. 如果是后续再次调用 getInstance , 由于instance 已经不再是 null, 此时不会进入 if , 直接返回之前创建好的引用了.?

这样设定, 既可以保证该类的实例是唯一一个, 于此同时, 创建实例的实际就不是程序驱动时了, 而是第一次调用 getInstance 的时候.?

线程安全问题

对于饿汉模式来说, getInstance 方法直接返回 Instance 实例, 这个操作本质就是"读操作", 多个线程读取同一个变量, 是线程安全的.

在懒汉模式中, 如下图所示,

有可能会创建出多个实例, 是线程不安全的.

下面是一个改进的代码:?

class SingletonLazy {
    private static SingletonLazy instance = null;
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        synchronized (locker) {
            if(instance == null) {
                instance = new SingletonLazy();
            }
            return instance;
        }
    }
    private SingletonLazy(){ };
}

这个代码虽然解决的线程不安全的问题, 但是每次执行这串代码的时候都要进行加锁解锁的操作, 这样程序的效率就变差了, 如果在加锁前先进行判断是否需要加锁, 就可以提高程序的效率了.

再次改进后的代码:

class SingletonLazy {
    private static SingletonLazy instance = null;
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        if(instance == null) {
            //如果 instance 为 null, 就说明是首次调用, 首次调用就要考虑线程安全问题, 就要加锁
            //如果是非 null, 就说明是后续调用, 就不用加锁了
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){ };
}

然而, 这段代码还有进一步改进的空间!

解决上述问题核心方法是使用 volatile 关键字

volatile 关键字有两个功能:

  1. 保证内存可见性, 每次访问变量必须要重新读取内存, 而不会优化到寄存器/缓存中
  2. 禁止使用指令重排序. 针对被 volatile 修饰的变量的读写操作相关指令, 是不能被重排序的.

最终修改后的代码:?

class SingletonLazy {
    public volatile SingletonLazy instance = null;
    Object locker = new Object();
    public SingletonLazy getInstance() {
        if(instance == null) {
            synchronized (locker) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }

    private SingletonLazy(){}
}

?

文章来源:https://blog.csdn.net/m0_73648729/article/details/134765046
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。