【学习笔记】设计模式-单例模式(Singleton)

0 设计模式

不了解设计模式的小伙伴可以通过这篇文章先了解一下什么是设计模式

https://blog.csdn.net/qq_42874315/article/details/120006447?spm=1001.2014.3001.5502

1 单例模式

创造型的设计模式,顾名思义会创造并仅创造一个对象实例,在程序的某些地方只需要一个实例即可,过多的实例会造成资源的浪费。

2 实现思路

2.1 饿汉式

描述: 类加载到内存后,就实例化一个单例,JVM保证线程安全

特点: 不管用不用,类加载时就完成实例化

2.2 懒汉式

描述:按需初始化实例

特点:虽然达到了按需初始化的目的,但是在多线程环境下有现成不安全的问题(可能会创建多个实例对象),要通过加锁去解决

2.3 静态内部类(推荐使用)

描述:JVM保证单例,JVM只加载一次外部类,在调用内部类后只加载一次内部类

特点:加载外部类时不会加载内部类,这样可以实现懒加载

2.4 枚举(推荐使用)

描述:通过枚举直接调用

特点:不仅可以解决线程同步,还可以防止反序列化(Effective Java中的最佳实现)

3 具体实现

3.1 饿汉式

3.1.1 常见的写法

public class Mgr01 {
    private static final Mgr01 INSTANCE = new Mgr01();
    private Mgr01(){};
    // 别的类想调只能用getInstance
    public static Mgr01 getInstance(){
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }
    public static void main(String[] args) {
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1==m2);
    }
}

3.1.2 另一种写法

和3.1.1的区别:将初始化放在了静态代码块

public class Mgr02 {
    private static final Mgr02 INSTANCE;
    static {
        INSTANCE = new Mgr02();
    }
    public static Mgr02 getInstance(){
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }
    public static void main(String[] args) {
        Mgr02 m1 = Mgr02.getInstance();
        Mgr02 m2 = Mgr02.getInstance();
        System.out.println(m1==m2);
    }
}

3.2 懒汉式

3.2.1 简单的懒汉式

public class Mgr03 {
    // 这里不能写final 写了final必须初始化
    private static Mgr03 INSTANCE;
    private Mgr03(){
    }
    public static Mgr03 getInstance(){
        if (INSTANCE == null){
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }
}

存在问题:线程不安全

加入以下代码模拟出线程不安全的情况

public class Mgr03 {
    // 这里不能写final 写了final必须初始化
    private static Mgr03 INSTANCE;
    private Mgr03(){
    }
    public static Mgr03 getInstance(){
        if (INSTANCE == null){
            // 通过使当前线程睡眠模拟实际开发中的一些情况,从而发现线程不安全的问题
            try{
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                // 打印出每个实例的hashcode,验证单例
                System.out.println(Mgr03.getInstance().hashCode());
            }).start();
        }
    }
}

解决方案:通过synchronized保证线程安全

3.2.2 通过synchronized解决线程不安全的问题

public class Mgr04 {
    // 这里不能写final 写了final必须初始化
    private static Mgr04 INSTANCE;
    private Mgr04(){
    }
    public static synchronized Mgr04 getInstance(){
        if (INSTANCE == null){
            INSTANCE = new Mgr04();
        }
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                // 打印出每个实例的hashcode,验证单例
                System.out.println(Mgr04.getInstance().hashCode());
            }).start();
        }
    }
}

存在问题:每次都要先判断锁,效率下降

解决方案:缩小同步代码块

3.2.3 缩小同步代码块

public class Mgr05 {
    // 这里不能写final 写了final必须初始化
    private static Mgr05 INSTANCE;
    private Mgr05(){
    }
    // 锁的是Mgr05.class对象
    public static Mgr05 getInstance(){
        if (INSTANCE == null){
            // 不可行!!!!!!!!! 反而不安全了
            synchronized (Mgr05.class){
                // 通过使当前线程睡眠模拟实际开发中的一些情况,从而发现线程不安全的问题
                try{
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Mgr05();
            }
        }
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                // 打印出每个实例的hashcode,验证单例
                System.out.println(Mgr05.getInstance().hashCode());
            }).start();
        }
    }
}

存在问题:无法确保只有一个实例对象,因为可能存在多个对象同时进入INSTANCE == null的情况,当一个线程创建完成对象之后,另外一个线程直接就可以创建对象了

解决方案:双重锁检查

3.2.4 双重检查

public class Mgr06 {
    // 这里不能写final 写了final必须初始化
    // volatile 禁止指令重排序
    private static volatile Mgr06 INSTANCE;
    private Mgr06(){
    }
    // 锁的是Mgr06.class对象
    public static Mgr06 getInstance(){
        // 双重检查
        if (INSTANCE == null){
            synchronized (Mgr06.class){
                // 双重检查
                if (INSTANCE == null) {
                    // 通过使当前线程睡眠模拟实际开发中的一些情况,从而发现线程不安全的问题
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                // 打印出每个实例的hashcode,验证单例
                System.out.println(Mgr06.getInstance().hashCode());
            }).start();
        }
    }
}

volatile关键字的作用

  1. 保证被volatile修饰的变量对所有线程是可见的
  2. 禁止指令重排序,这样其他线程不会访问到一个未初始化的空对象

3.3 静态内部类

public class Mgr07 {
    private Mgr07(){

    }
    private static class Mgr07Holder{
        private final static Mgr07 INSTANCE = new Mgr07();
    }
    public static Mgr07 getInstance(){
        return Mgr07Holder.INSTANCE;
    }
    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                // 打印出每个实例的hashcode,验证单例
                System.out.println(Mgr07.getInstance().hashCode());
            }).start();
        }
    }
}

3.4 枚举

public enum Mgr08 {
    INSTANCE;
    public void m(){
		System.out.println("我是一个单例鸭");
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
	            // 打印出每个实例的hashcode,验证单例
                System.out.println(Mgr08.INSTANCE.hashCode());
            }).start();
        }
    }
}

4 思维导图

在这里插入图片描述

5 示例源码地址

https://github.com/ChenJiahao0205/design-pattern/tree/master

最后

我是通过马士兵老师的视频和菜鸟教程学习的,部分内容可能会有雷同

想阅读更多设计模式相关文章,欢迎到我的专栏【学习笔记】、【设计模式】中去查看

在23篇设计模式文章发布完成之后,我会公开完整的思维导图,点关注,不迷路

感谢大家看到这里,文章如有不足,欢迎大家指出;彦祖点个赞吧彦祖点个赞吧彦祖点个赞吧,欢迎大家关注和转发文章!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

五条Programmer

比心~

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

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

打赏作者

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

抵扣说明:

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

余额充值