单例模式的写法

简介: 单例模式的写法

单例模式:类在进程中只有唯一的一个实例

解释一下:

可以把猫理解为一个类

它的实例有:橘猫,布偶,美短,虎斑…

这里介绍2种较为常见的单例模式的写法

  • 饿汉模式(急迫)
  • 懒汉模式(从容)

🔎1.示例(饿汉模式)

饿汉模式:线程天然就是安全的

什么是饿汉模式呢?

举个栗子

有一个人已经一天没吃饭了

他很饿很饿,这时候如果有一桌饭菜摆在他面前,他是很急迫的渴望去吃饭的

🌻示例代码

class Single {
    private static Single instance = new Single();
    public static Single getInstance() {
        return instance;
    }
    private Single() {
    }
}
public class Test1 {
    public static void main(String[] args) {
        Single s1 = Single.getInstance();
        //注意这里会报错,因为构造方法被private修饰
        //Single s2 = new Single();
    }
}
• 1
• 2
• 3
• 4
• 5
• 6
• 7
• 8
• 9
• 10
• 11
• 12
• 13
• 14
• 15
• 16
• 17
• 18
• 19
• 20
• 21

上述代码因为构造方法被private修饰,所以只能有一个实例(单例模式)

注意:这段代码通过反射仍然可以获得一个新的实例(这里不讨论这种情况)

那么为什么饿汉模式天然就是安全的呢?

回想以下可能导致线程安全问题的几点原因:

(1)抢占式执行 (2)多个线程修改同一个变量 (3)操作不是原子性的 (4)内存可见性 (5)指令重排序

上述代码只有一个return instance,只是读取instance,虽然会发生抢占式执行,但并没有修改变量

所以饿汉模式天然就是安全的


🔎2.示例(懒汉模式)

懒汉模式:多线程下可能无法保证创建对象的唯一性(不安全)

什么是懒汉模式呢?

举个栗子

你和你的女朋友刚刚在家吃过晚饭

吃饭的碗可以现在不去刷,等到下次再吃饭的时候再去洗碗

这就是懒汉模式,比较从容


🌻示例代码

class SingleLazy {
    private static SingleLazy instance = null;
    public static SingleLazy getInstance() {
        if(instance == null) {
            instance = new SingleLazy();
        }
        return instance;
    }
    private SingleLazy() {
    }
}
public class Test2 {
    public static void main(String[] args) {
        SingleLazy s1 = SingleLazy.getInstance();
        //注意这里会报错,因为构造方法被private修饰
        //Single s2 = new Single();
    }
}

上述代码因为构造方法被private修饰,所以只能有一个实例(单例模式)

注意:这段代码通过反射仍然可以获得一个新的实例(这里不讨论这种情况)


🌻原因分析

这里我们来一点点剖析上面的代码

🌼case1

我们看到这里面 instance默认是null,而不是new一个实例,这和饿汉模式是有区别的

(1)那么为什么这么做呢?

(2)到底是通过饿汉模式书写单例模式更好还是通过懒汉模式书写单例模式更好呢?

(1)这样做是为了避免有可能我并没有调用该类.却把类实例化了,造成了浪费(注:这里只是通过这个这个例子解释区别)

(2)答案是通过懒汉模式,想象以下,有一个10G的文件,饿汉模式是全部加载完才能够打开(这可能需要几分钟),懒汉模式是加载显示器能显示的那一部分,然后再用再去加载,虽然对硬盘的访问次数多了,但是还是懒汉模式更香啊!!!


🌼case2

安全性分析

对于这段代码,如果多个线程同时调用,就可能产生多个对象了

如图

当 t1线程先去判断 instace == null,发现 instance是null

此时 t2线程也正好去判断 instace == null,发现 instance是null

然后 t1和 t2线程分别去执行 new SingleLazy()这个操作,就可能会产生多个对象


解决方法

通过加锁🔒,合并2个步骤为1个步骤,变成原子性

public static SingleLazy getInstance() {
        //注意这里的是静态方法,()里面需要写成(类.class)
        synchronized (SingleLazy.class) {
            if (instance == null) {
                instance = new SingleLazy();
            }
        }
        return instance;
}

🌼case3

对case2改动代码进行优化

我们知道,加锁🔒就会产生阻塞(运行速度变慢)

但是不加锁🔒,数据的准确性又会产生问题,这里我们能不能进行一点点优化呢?

请看代码

public static SingleLazy getInstance() {
        //注意这里的是静态方法,()里面需要写成(类.class)
        if(instance == null) {
            synchronized (SingleLazy.class) {
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
        }
        return instance;
}

注意看这2段代码的区别,多了个 if(instance == null)

不加 if()进行判断,每次都得去加锁阻塞,无论此时的 instance是否为null

加上if()后,如果 istance不是null直接返回,是再去进行加锁操作

这样可以省去了不少时间


回顾一下

对于刚开始的代码

这里是存在安全问题的,所以我们需要加锁🔒去保证安全

当加锁🔒后,无论此时的 instance是否为null,都需要去执行加锁操作

于是就进行了一点优化

🌼case4

安全性分析

那么针对这段代码,是否就没有问题了呢?

指令重排序可能会产生错误

红色圈圈代表可能会触发指令重排序问题

指令:

(1)创建内存

(2)调用构造方法

(3)将内存地址赋给引用

对于上述指令

其执行顺序可能为(1)(2)(3)

也可能是(1)(3)(2)

当执行顺序为(1)(3)(2)时

先创建了内存,然后将内存地址赋给引用,如果此时其他线程去调用 getInstance方法,就会发现 instance不是 null,但是由于并没有进行步骤(2)调用构造方法,也就不是一个实例,所以就会引发错误

解决方法

加volatile禁止指令重排序

完整代码

class SingleLazy {
    volatile private static SingleLazy instance = null;
    public static SingleLazy getInstance() {
        //注意这里的是静态方法,()里面需要写成(类.class)
        if(instance == null) {
            synchronized (SingleLazy.class) {
                if (instance == null) {
                    instance = new SingleLazy();
                }
            }
        }
        return instance;
    }
    private SingleLazy() {
    }
}
public class Test2 {
    public static void main(String[] args) {
        SingleLazy s1 = SingleLazy.getInstance();
        //注意这里会报错,因为构造方法被private修饰
        //Single s2 = new Single();
    }
}

🔎3.总结

饿汉模式:天然就是安全的

懒汉模式:不安全

  • 解决方法:
  • 加锁,将 if 和 new 操作结合成一起
  • 双层if,进行优化
  • 使用volatile禁止指令重排序

🔎结尾

创作不易,如果对您有帮助,希望您能点个免费的赞👍

大家有什么不太理解的,可以私信或者评论区留言,一起加油

相关文章
|
1月前
|
设计模式 安全
单例模式的几种写法
【10月更文挑战第10天】
94 61
|
6月前
单例模式例子
单例模式例子
|
安全 Java
单例模式的8种写法
单例模式的8种写法
113 0
单例模式的8种写法
|
SQL 设计模式 安全
6. 单例模式有几种写法?
6. 单例模式有几种写法?
103 0
6. 单例模式有几种写法?
|
设计模式 安全 Java
设计模式-Singleton单例模式详解以及8种写法
设计模式-Singleton单例模式详解以及8种写法
|
SQL 设计模式 安全
|
设计模式 SQL 存储
【面试干货】单例模式的七种写法
【面试干货】单例模式的七种写法
175 0
【面试干货】单例模式的七种写法
|
设计模式 安全 Java
单例模式的七种写法,你都知道吗?
单例模式的七种写法,你都知道吗?
192 0
单例模式的七种写法,你都知道吗?
|
设计模式 安全
论单例的写法
有一回对我说道,“你学过设计模式么?”我略略点一点头。他说,“学过,……我便考你一考.设计模式的单例,怎样写的?”我想,讨饭一样的人,也配考我么?便回过脸去,不再理会.孔乙己等了许久,很恳切的说道,“不能写罢?……我教给你,记着!这些模式应该记着.将来当面试官的时候,面试要用.”我暗想我和面试官的等级还很远呢又好笑,又不耐烦,懒懒的答他道,“谁要你教?”孔乙己显出极高兴的样子,将两个指头的长指甲敲着柜台,点头说,“单例有六种写法,你知道么?
189 0