本文介绍: 单例模式是一种设计模式,其中设计模式是软性的规定,与它关联框架是硬性的规定,这些都是大佬已经设计好了的,即使是代码写的不是很好的菜鸡,按照这种模式也能写出还行的代码。类似象棋中的棋谱,即使你是新手,但按着棋谱走,你的棋力也不会太差。单例 = 单个实例对象);某个类,在一个线程中,只应该创建一个对象原则上不应该多个),这时就使用单例模式,就可以我们代码进行一个更严格校验检查。那么,怎么保证这一个对象唯一呢?

目录

一、单例模式的简单介绍

二、饿汉模式

三、懒汉模式

四、饿汉模式和懒汉模式的线程安全问题分析

1、饿汉模式(线程安全)

2、懒汉模式(线程不安全)

解决懒汉模式的线程安全问题

①给写操作打包成原子

②去除冗余操作

③存在指令重排序的问题

3、解决懒汉模式线程安全问题的最终代码:


一、单例模式简单介绍

单例模式是一种设计模式,其中设计模式是软性的规定,与它关联框架是硬性的规定,这些都是大佬已经设计好了的,即使是代码写的不是很好的菜鸡,按照这种模式也能写出还行的代码。类似象棋中的棋谱,即使你是新手,但按着棋谱走,你的棋力也不会太差。

单例 = 单个实例对象某个类,在一个线程中,只应该创建一个对象原则上不应该有多个),这时就使用单例模式,就可以我们代码进行一个更严格校验和检查

那么,怎么保证这一个对象唯一呢?

其一方法可以通过“君子约定”,写一个文档,规定这个类只能有唯一的实例,新手程序猿接手这个代码时,就会发一份这个文档,进行约定,熟悉其中的规定、条约。

其二方法可以机器我们检查,人肯定是没有机器靠谱的,我们期望机器我们代码指定的类,创建类的实例个数进行检查、校验,当创建的实例个数超过我们期望个数,就编译报错,这一点还是实现的,其中单例模式就是已经设计好的套路,可以实现这种预期效果


二、饿汉模式

饿汉模式是指创建实例是时期非常早,在类加载时候程序启动,就已经创建好实例了,使用饿汉”这个词,就是形容创建实例非常迫切,非常早。下面实现一个单例模式

代码

class Singleton {
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
    private Singleton(){ }
}
public class TestDemo4 {
    public static void main(String[] args) {
        
    }
}

我们想在主线程中创建一个Singleton的实例时,会报错如图

singleton类的代码解析

singleton内部第一行代码就是这个,如图

说明singleton内部一开始就创建好了实例,创建实例非常迫切,使用static修饰说明instance是类属性

接下来获取这个类的实例方法,如图

因为我们不希望能创建出多个实例,所以就把singleton构造方法private来修饰,如图:

这样,如果我们new一个Singleton对象,也new不了,但也有非正规手段,去获取singleton里面属性方法反射

最后,不管我们用getInstance获取多少次实例,获取的对象都是同一个对象验证如下

代码:

class Singleton {
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
    private Singleton(){ }
}
public class TestDemo4 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);
    }
}

执行结果


三、懒汉模式

饿汉模式不一样的是,创建实例的时机比较晚,没饿汉创建实例那么迫切,只有第一次使用这个类时,才会创建实例。

代码如下

class SingletonLazy {
    private static SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if(instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
    private SingletonLazy() { }
}
public class TestDemo5 {
    public static void main(String[] args) {

    }
}

代码解析

一开始,没有创建实例,只是给singletonLazy赋值null,并没有new一个对象,也就是没有创建实例;首次调用getInstance,instancenull的,所以会new一个对象,创建实例。如果不是第一次调用getInstance,就直接返回instance,这也保证了这个类的实例是唯一的,只有一个实例;

饿汉模式的区别就是没那么迫切创建实例,等需要调用这个类的时候才创建一个实例,而饿汉模式是有了这个类就创建出实例。

懒汉模式的优点:有的程序,要在一定条件下,才需要进行相关操作,有时候不满足这个条件,也就不需要完成这个操作了,这样,就把这个操作省下来了,而懒汉模式,就是这一思想,当需要这个实例时,才创建实例。像肯德基的疯狂星期四,只有在星期四的时候才会加载相关信息,其他时间就不会加载。


四、饿汉模式和懒汉模式的线程安全问题分析

1、饿汉模式(线程安全

代码分析

当有多个线程,同时并发执行,调用getInstance方法,取instance,这时,线程安全的吗?显然。这是线程安全操作,因为只涉及到读,多线程读取同一个变量,是线程安全的。而instance很早之前就已经创建好了,不会修改它,一直也只有这一个实例,也不涉及写的操作。

2、懒汉模式(线程不安全)

代码分析

这里调用getInstance方法后,就会创建出实例来,那么我们想想,如果多个线程同时调用这个方法,此时SingletonLazy类里面的instance都为null,那么这些线程都会new对象,就会创建多个实例,这时,就不符合我们单例模式的预期了,所以,这个代码是线程不安全的。

这也是线程不安全的直接原因,就是 “写” 操作不是原子的。

解决懒汉模式的线程安全问题

①给写操作打包原子

因为多线程并发执行的时候,可能读到的都是instance == null,所以会创建多个实例,那我们就给它加锁,让它在创建实例的时候,只能创建一个。

代码:

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

这样,能让写操作的时候打包成一个原子,实例只可能创建一个的情况。

②去除冗余操作

以上操作加上了还是问题:如果已经创建出实例了,我们还有加锁判断它是不是null吗,加锁这些操作也是要消耗硬件资源的,没有必要为此浪费资源空间,如果已经不是null了,我们就想让它直接返回,不再进行加锁操作。

代码:

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

注意看这里两个判断条件是一样的,目前位置,也应该是我们第一次遇到这种代码,为什么要套两层一模一样的代码呢?要注意,这里两个条件表达的都是不同的意思,第一个if条件判断是要让instance==null时,才要加锁的意思,提升代码的执行效率;而第二个if条件判断是把写操作打包成一个原子,保证线程安全。这两个if条件判断所表达的意思是不同的。

存在指令排序问题

指令排序指令重排序也是编译器的一种优化,在保证原代码的逻辑不变,调整原代码的指令执行顺序,从而让程序执行效率提高

举个栗子:

现在我们要去菜市场买菜,买菜的清单黄瓜,西红柿,萝卜,茄子,卖这些菜的位置分布图:

如果按照买菜清单顺序路线是这样的:

但是我们如果改变顺序,就能缩短我们买菜的时间了,如图:

指令重排序也是类似的道理,保证原代码的逻辑不变,改变原有指令的顺序,从而提高代码的执行效率,其中这个代码,就存在着指令重排序优化,如图:

原本指令执行顺序:

1、去内存申请一段空间

2、在这个内存中调用构造方法,创建实例

3、从内存取出地址赋值给这个实例instance。

指令重排序后的顺序:1, 3 , 2

按照指令重排序后的代码执行逻辑就变成了这样:

假设两个线程,现在执行顺序是这样的,如图:

因为指令重排序后,先去内存申请一段空间然后赋值给instance,那这时,instance就不是null了,第二个线程不会进入到if语句了,直接返回instance,可是instance还没有创建出实例,这样返回肯定是有问题的,这样,也就线程不安全了。

解决方案给instance这个变量,加volatile修饰,强制取消编译器优化,不能指令重排序,同时也排除了内存可见性的问题

volatile后的代码:

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

到这一步,才真的解决懒汉模式的线程安全问题。

3、解决懒汉模式线程安全问题的最终代码:

class SingletonLazy {
    private static Object locker = new Object();
    private static volatile SingletonLazy instance = null;
    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() { }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

main线程是来测试每次拿到SingletonLazy的实例是不是同一个,

执行结果:符合我们的预期

原文地址:https://blog.csdn.net/cool_tao6/article/details/134742040

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_31532.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注