单例模式:双重效验锁的懒汉实现方式

单例模式:双重效验锁的懒汉实现方式

单例模式

单例模式顾名思义就是指实例化一个对象,通过把构造方法私有化来禁止创建实例。

饿汉模式实现

饿汉模式的特点是在类加载的时候就创建并初始化一个实例,实例在整个程序运行期间都是唯一的

java 复制代码
public class Singleton {
    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}

这里的getInstance()必须是静态方法,因为静态方法可以通过类名.方法名的方式调用,而非静态方法则要创建实例,这与我们单例模式的规则不相符。

懒汉模式实现

懒汉模式的特点是需要的时候再创建实例,实例在整个程序运行期间都是唯一的。

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (null == instance) {
           instance = new Singleton();
        }
        return instance;
    }
}

上面代码在单线程模式下是没问题的,但是在多线程模式下就会存在线程安全问题。

如果在首次创建实例,多个线程同时调用getInstance()方法,就有可能创建多个实例。

改进1 (创建多个实例)

我们可以进行对 getInstance() 方法进行加锁:

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
}

这样虽然解决刚刚线程安全的问题,但每次调用getInstance()方法都要加锁,增加不少的开销。

我们发现上面线程安全问题只存在于首次创建实例的情况,因此我们只需要对instance为空的时候单独处理即可。

改进2 (性能比较低)

instance为空的时候加锁,再判断一次是否为空即可:

java 复制代码
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

粗略的看上去好像没什么问题,实际上这里还有一个指令重排序的问题

通过查阅资料知道instance = new Singleton();这个代码在执行的时候实际是执行分3步骤执行:

java 复制代码
memory = allocate(); //1.分配对象的内存空间
ctorInstance(memory); //2. 初始化对象
instance=memory; //3. 设置instance指向刚分配的内存地址

JVM在执行的时候可能就会优化成 1->3->2的顺序执行

可能导致在多线程环境下,还没执行2就已经被其他线程返回了一个刚分配的地址

同样存在线程安全问题,需要使用volatile关键字来禁止指令重排序

改进3 (volatile禁止指令重排序)

java 复制代码
public class Singleton {
    //volatile 防止指令重排 和可见性
    private static volatile Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
         //先判断对象是否已经实例化过,没有实例化才进入加锁代码
        if (null == instance) {
            //类对象加锁
            synchronized (Singleton.class) {
                //避免 singleTon== null时,第一个线程实例化后,进入阻塞状态的线程被唤醒后仍会进行实例化。
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

以上就是线程安全的懒汉模式。

相关推荐
0白露43 分钟前
Apifox Helper 与 Swagger3 区别
开发语言
Tanecious.2 小时前
机器视觉--python基础语法
开发语言·python
叠叠乐2 小时前
rust Send Sync 以及对象安全和对象不安全
开发语言·安全·rust
战族狼魂2 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
Tttian6223 小时前
Python办公自动化(3)对Excel的操作
开发语言·python·excel
xyliiiiiL3 小时前
ZGC初步了解
java·jvm·算法
杉之4 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
hycccccch4 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
独好紫罗兰4 小时前
洛谷题单2-P5713 【深基3.例5】洛谷团队系统-python-流程图重构
开发语言·python·算法
天天向上杰5 小时前
面基JavaEE银行金融业务逻辑层处理金融数据类型BigDecimal
java·bigdecimal