设计模式 — 单例模式

目录

什么是设计模式?

一、什么是单例模式

二、介绍”饿汉“和”懒汉“模式

三、”饿汉“和”懒汉“模式代码实现

1.饿汉模式

2.懒汉模式

四、单例模式 ——线程安全问题(针对饿汉模式和懒汉模式)

1.饿汉模式 ——安全

2.懒汉模式——不安全

五、解决线程安全的方案

1.加锁

2.new对象的指令重排序问题


什么是设计模式?

设计模式是面向对象设计中反复出现的问题的解决方案,通常描述了一组相互紧密作用的类与对象。大概就是比如针对软件开发中的一些常见的“场景问题”出了一些固定的套路来实现代码。

单例模式就是设计模式中的一种。

一、什么是单例模式

单例:就是指单个实例,只能创建一个对象/实例。

单例模式能保证某个类在程序种只存在唯一实例,而不会创建出多个实例。

单例模式具体的实现方式,下面主要介绍两种写法:“饿汉模式''和”懒汉模式“。

二、介绍”饿汉“和”懒汉“模式

(1).饿汉模式

举例说明:吃完饭之后,就立即去洗碗

(2).懒汉模式

举例说明:吃完之后,碗放槽里,先不洗,等到下一顿吃的时候,需要用了再去洗碗。

通常认为懒汉模式效率更高。

针对两种模式,举例:打开一个硬盘上的文件,读取文件内容,并且显示出来。

饿汉模式:把所有的内容读取到内存种,并显示出来。

如果内容过多,用户在使用时,可能会卡顿很久,内存也不一定够。

懒汉模式:只把文件都一小部分(比如看小说看文),把当前屏幕填充上,如果用户翻页了,在读取其他内容,不过不翻页,就省下了。懒汉模式可以快速打开内容,比饿汉模式效率更高,非必要不读入。

三、”饿汉“和”懒汉“模式代码实现

1.饿汉模式

//把这个类设置为单例的
class Singleton{
    //创建唯一的实例
    private static Singleton instance = new Singleton(); //被static修饰,表示该属性是类的属性
    //获取到实例的方法  读操作
    public static Singleton getInstance() {
        return  instance;
    }
    //在类内部把实例创建好,同时禁止外部new实例,这样就可以保证单例模式的特性
    private Singleton() {
    }
}
public class ThreadDemo17 {
    public static void main(String[] args) {
        //此时s1与s2是一个实例,是唯一的实例
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
         //Singleton s3 = new Singleton();//如果再new一个实例,就会报错
    }
}

2.懒汉模式

class SingletonLazy {
    //创建唯一的实例
    private  static SingletonLazy instance = null;
    
    public  static SingletonLazy getInstance() {
        if (instance == null) {
                    instance = new SingletonLazy();
        }
        return instance;
    }
    //在类内部把实例创建好,同时禁止外部new实例,这样就可以保证单例模式的特性
    private SingletonLazy() {
​
    }
    public class ThreadDemo18 {
    public static void main(String[] args) {
        //此时s1与s2是一个实例,是唯一的实例
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);//true
    }
}

四、单例模式 ——线程安全问题(针对饿汉模式和懒汉模式)

对于多线程安全,我们可以知道:

一个线程修改同一个变量 → 安全

一个线程读取同一个变量 → 安全

多个线程修改同一个变量 → 不安全

多个线程读取同一个变量 → 安全

1.饿汉模式 ——安全

首先饿汉模式,根据以下代码(与前面写的代码一致):

//获取到实例的方法  读操作
    public static Singleton getInstance() {
        return  instance;
    }

这里的return instance只是一个读操作,是安全的。

2.懒汉模式——不安全

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

首先,懒汉模式要去new对象,假如此时有多个线程,然后多个线程执行顺序紊乱,比如此时有两个线程,一个t1线程,一个t2线程,如下,假如执行顺序如下:

 如上图这样,就可能导致产生了两个对象,这样就不是单例模式了,是一个很危险的操作,如果产生一个对象消耗的内存过大,这种操作可能就会导致内存负荷很大,很危险。

所以总结来说,出现上述问题的原因主要还是因为上面的操作不是原子的,所以想要解决这个问题就需要将上面的操作变成原子的。

五、解决线程安全的方案

1.加锁

(1)把if与new变成原子操作;

(2)双重if,减少不必要的加锁操作。

//保证判定和new是一个原子操作
        //这个条件,判定是否要加锁,如果对象已经有了,就不必加锁了,此时本身就是线程安全
        if (instance == null) {//判断是否要加锁
            synchronized (SingletonLazy.class){
                if (instance == null) {//判断是否要创建对象
                    instance = new SingletonLazy();
                }
            }
        }

以上代码,一定要首先判定是否要加锁,因为不知道内存里面是否已经有instance对象了,如果不加这个判定,就会导致又创建出一个对象,就会导致不是单例模式,导致线程不安全。

2.new对象的指令重排序问题

instance = new SingletonLazy();

首先这个new对象操作,分为三个指令:

1.创建内存;

2.调用构造方法;

3.把内存地址付给引用。

关于这三个指令,2和3的执行顺序是随机的,有可能是下面这种情况:

1.创建内存;

3.把内存地址付给引用。

2.调用构造方法;

如果是以上的情况,在多线程的环境下,可能会出现问题。

假如t1以以上方式执行到了3,因为编译器优化指令重排序,系统调度给t2了,再去判定条件,发现条件不成立,非空 ,就直接返回了实例的引用,但是实际上,上一步操作其实还没有调用构造方法,后面就出现了问题。

所以我们需要使用volatile关键字:

volatile private  static SingletonLazy instance = null;

所以前面懒汉模式改良后的代码如下:

package threading;
//懒汉模式:非必要,不创建
class SingletonLazy {
    //加volatile主要是为了防止创建对象重排序,保证内存稳定性
    //创建对象分为三步:
    //1.申请内存空间
    //2.调用构造方法
    //3.把内存地址,赋给引用
    //其中2,3项是打乱进行的,假如出现了一个极端情况,t1执行1,3操作以后
    // ,系统调度t2,导致t1还没有调用构造方法,然后后导致判定条件不成立,非空,
    // 直接返回实例的引用,导致t2直接调用的该实例
    volatile private  static SingletonLazy instance = null;
​
    //只有调用了getInstance 才会去创建实例
    public  static SingletonLazy getInstance() {
        //保证判定和new是一个原子操作
        //这个条件,判定是否要加锁,如果对象已经有了,就不必加锁了,此时本身就是线程安全
        if (instance == null) {//判断是否要加锁
            synchronized (SingletonLazy.class){
                if (instance == null) {//判断是否要创建对象
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
​
    private SingletonLazy() {
​
    }
}
public class ThreadDemo18 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值