【七】一文带你迅速掌握设计模式中的单例模式

1. 什么是设计模式

设计模式可以理解为就是一种固定套路,就好比你和对手下棋得时候,会有一些固定套路下法;而设计模式就是软件开发的棋谱~
设计模式有很多种,接下来就介绍一种校招阶段,主要考察的两种设计模式:

  • 单例模式
  • 工厂模式

2. 单例模式

所谓的单例模式就是单个实例(对象),进一步的解释就是在一个程序中,某个类,只能创建出一个实例(一个对象),不能创建多个实例(多个对象)
在Java中的单例模式,借助Java语法,保证某个类,只能够创建出一个实例,而不能new多次~~
那为什么要有这个单例模式式呢?是因为在有些场景中,本身就要求某个概念是单例的~

Java中实现单例模式有很多种写法,接下来主要介绍两种

  • 饿汉模式
  • 懒汉模式

可以举一个计算机中的例子来解释下什么是饿汉模式,什么是懒汉模式
假设我们打开一个硬盘上的文件,读取文件内容,并显示出来
饿汉模式:把文件所有内容都读到内存中,并显示
懒汉模式:只把文件读一小部分,把当前屏幕填充上,如果用户翻页了,再读其他文件内容,如果不翻页,就省下了~
这种情况下,如果文件非常大,饿汉模式就可能打开卡半天,但是懒汉模式可以快速打开~

2.1 饿汉模式(急迫)

class Singleton{
    // 唯一实例的本体
    private static Singleton instance = new Singleton();

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

    // 禁止外部 new 实例
    private Singleton(){

    }
}
public class Demo13 {
    public static void main(String[] args) {
        // 此时 s1 和 s2 是同一个对象
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        
        // Singleton s3 = new Singleton();  此处不允许 new
    }
}

上述代码就是饿汉模式!但是有个缺点,就是在类加载的时候,就完成了实例的创建,那如果后面没有用到这个实例呢? 接下来我们介绍另外一个懒汉模式~

2.2 懒汉模式(从容)

class SingletonLazy {
    private static SingletonLazy instance = null;

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

    private SingletonLazy() {
    }
}
public class Demo14 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

上述代码,就是懒汉模式,在调用的时候,才会真正去创建实例~

2.3 多线程下不安全

我们对比下 饿汉模式 和 懒汉模式分析下哪一个在多线程下是安全的?
在这里插入图片描述
通过对比可以,分析出 懒汉模式在多线程情况下是会不安全的!下面进行详细分析,在多线程下,懒汉模式可能无法保证创建对象的唯一性~

在这里插入图片描述

2.3.1 饿汉模式(多线程版)

class SingletonLazy {
    private static SingletonLazy instance = null;

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

    private SingletonLazy() {
    }
}

public class Demo14 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

只有确保 判定 和 new 操作是原子性的操作才能保证多线程操作下的安全! 但是其实加锁是一个比较低效的操作!(加锁就可能涉及到阻塞等待),所以一般的非必要不要加锁~

2.3.2 饿汉模式(多线程改进版)

由于上述代码中,只要调用getInstance就会触发锁竞争!~其实我们只要一分析一开始懒汉模式的代码,就会发现,此处的线程不安全操作,只出现了首次创建实例的处,一旦实例创建好以后,后续调用getInstance,只是读操作!,所以可以将代码进行优化

class SingletonLazy {
    private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        // 这个条件,判断是否要加锁,如果对象已经有了,就不必加锁了,此时
        if (instance == null) {
            synchronized (SingletonLazy.class) {
            	// 这个条件是判断对象是否为空
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
            return instance;
        }
    private SingletonLazy() {}
    }

    public class Demo14 {
        public static void main(String[] args) {
            SingletonLazy s1 = SingletonLazy.getInstance();
            SingletonLazy s2 = SingletonLazy.getInstance();
            System.out.println(s1 == s2);
        }
    }

只要在外层,再次加一个判定条件,这样就可以减少不必要的锁竞争。

2.3.2 饿汉模式 (多线程最终版)

尽管上述代码,已经进行了优化,但是仍旧存在一个问题,指令重排序问题!
在这里插入图片描述
所以就需要加 volatile 关键字保证指令重排序问题

class SingletonLazy {
    volatile private static SingletonLazy instance = null;

    public static SingletonLazy getInstance() {
        // 这个条件,判断是否要加锁,如果对象已经有了,就不必加锁了,
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
            return instance;
        }
    private SingletonLazy() {}
    }

    public class Demo14 {
        public static void main(String[] args) {
            SingletonLazy s1 = SingletonLazy.getInstance();
            SingletonLazy s2 = SingletonLazy.getInstance();
            System.out.println(s1 == s2);
        }
    }

2.4 总结

  • 饿汉模式:天然线程安全,只是读操作
  • 懒汉模式:不安全的,有读也有写
    • 加锁,把 if 和 new 变成原子操作
    • 双重 if,减少不必要的加锁操作
    • 使用 volatile 禁止指令重排序,保证后续线程肯定能拿到的是完整对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个想打拳的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值