首页 > 其他分享 >单例模式

单例模式

时间:2023-10-21 23:23:18浏览次数:28  
标签:Singleton 模式 instance 线程 private 单例 static public

目录

单例模式

属于创建者模式,提供了一种创建对象的方式

单例有两种设计形式
饿汉式 -- 类加载的时候,这个对象就会被创建
懒汉式 -- 只有首次使用的时候,才会创建对象

饿汉式

想要在类加载的时候创建对象,有两种方式
一种是在静态变量初始化时,赋值为一个对象
另一种是在静态代码块里给静态变量赋值为一个对象

优点

  • 线程安全,因为实例是在类加载过程中被创建的,类加载过程在JVM中是线程安全的

缺点

  • 这两种方式都可能造成内存浪费问题,因为类加载时就创建对象了,如果对象很大,又一直没使用,就很占空间
// 方式一:静态变量创建对象
public class Singleton {

    private Singleton() {};

    private static Singleton instance = new Singleton();

    public static Singleton getInstance() {
        return instance;
    }
}
// 方式二:使用静态代码块创建对象
public class Singleton {

    private Singleton() {};

    private Singleton instance;

    static {
        instance = new Singleton();
    }

    public static Singleton getInstance() {
        return instance;
    }
}

懒汉式

调用静态方法时才会创建对象,所以将创建对象写在静态方法里
(类加载过程中静态方法会被加载到内存中,但只有在被调用时才会执行)

  1. 线程不安全写法
public class Singleton {
    
    private Singleton() {};

    private static Singleton instance;

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

        return instance;
    } 
}
  1. 加同步锁,线程安全

缺点
对整个方法加了同步锁,效率低,每个时间点只有一个线程能执行这个方法

  • 这个同步锁加在静态方法上,相当于锁住了class对象(类对象),这个是会影响到其它的加了同步锁的静态方法执行,因为只有一个类对象
  • 但对于单例对象来说无所谓,因为它就这一个静态方法
public class Singleton {
    
    private Singleton() {};

    private static Singleton instance;

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            // 位置 1
            instance = new Singleton();

        }

        return instance;
    } 
}
  • 这个线程安全问题是在初始化instance时出现的,我们担心会有多个线程同时进入位置1(这是并行,如果并发该这么说:当一个线程执行到位置1时,切换到了另一个线程,那他们两个都能创建对象),从而导致new了多个实例,当初始化之后就不用担心线程安全问题了,所以没必要让每个线程持有锁才能调用这个方法
  1. 双重检查锁

这个双重检查锁的目的就是为了当instance初始化之后,其它线程不用再去拿锁或者等待锁了

public class Singleton {
    private Singleton() {};

    private static Singleton instance;

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

        return instance;
    }
}

但这还是有问题,因为instance = new Singleton();可能会发生指令重排
为什么会发生指令重排?
因为jvm在实例化对象的时候
这行代码,在字节码指令中有两步

    1. 调用构造方法
    1. 将这个对象的引用赋值给instance

从jvm的角度来说

    1. 给对象分配内存
    1. 初始化
    1. 将对象地址赋值给变量

如果先执行了第2步,会给instance赋值一个未初始化的对象,然后这个时候这个线程时间片到了,另一个线程执行这个方法会直接返回这个未初始化的对象,所以为了保证先执行第一步,需要添加volatile关键字

  1. 防指令重排
public class Singleton {
    private Singleton() {};

    private static volatile Singleton instance;

    public static Singleton getInstance() {
        
        if (instance == null) {

            synchronized (Singleton.class) {
                
            }
        }

        return instance;
    }
} 
  1. 静态内部类
    线程安全,因为类加载是线程安全的
public class Singleton {
    private Singleton() {};

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
  1. 枚举方式
    线程安全,只会被装载一次,且不会被破坏
    在类加载时加载,且所有实例是在类加载阶段被初始化的,且只会被初始化一次
    饿汉式
public enum Singleton {
    INSTANCE;   // 枚举常量,也叫枚举实例
}

工厂模式

简单工厂模式

包括
抽象产品
具体产品
具体工厂

优点
将对象创建与业务逻辑分开,可以避免修改客户端代码,且更好扩展

缺点
增加新产品时,需要改变具体工厂的代码,违反了“开闭原则”

工厂方法模式

包括
抽象工厂
具体工厂
抽象产品
具体产品

具体来说,具体类实现或者继承抽象类,具体工厂实例化对象,
然后会有一个中间类使用多态的方式根据输入是什么工厂就返回一个什么实例
客户端想要什么产品就构建一个什么工厂对象,通过中间类得到该产品

优点
将对象创建与业务逻辑分开,实现代码解耦,实现在不修改客户端代码的情况下能更好扩展
用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

适用
适用于生产同一类产品,他们都有一个公共的特征

工厂模式应用

在我了解的JDK源码中,单列集合获取迭代器的方法就使用到了工厂方法模式
Collention是抽象工厂、ArrayList是具体工厂,Iterator是抽象产品、ArrayList类中的Iter内部类是具体的产品;
在ArrayList的源码中,他有一个iterator方法生产Iter对象,所以我们可以直接使用这个iterator方法得到集合中的元素

// ArrayList.java

public Iterator<E> iterator() {
        return new Itr();
}

/**
 * An optimized version of AbstractList.Itr
 */
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    Itr() {}

    ...
}

抽象工厂

每一类产品多了一个等级的维度

策略模式

行为型模型

定义了一系列算法,并把每个算法封账,可以相互替换

把算法的使用 与 算法的实现分开,并委派给不同的对象对这些算法进行管理

主要包括:
抽象策略
具体策略
环境

使用场景:
一个系统需要动态地在几种算法中选择一种,可以将每个算法封装到策略中
甚至可以用户自定义策略,根据这个策略实现具体功能

应用举例:
Arrays.sort()里面使用了 策略模式,在这个方法里我们需要传递一个Comparator接口的实现类,通过这个实现类我们可以自定义排序
Comparator接口就是抽象策略,
具体策略就是我们自己写的Comparator接口实现类
Arrays就是一个环境角色类

public class Arrays{
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }
}

责任链模式

如果处理一个事情,有多个流程,每个流程处理完要传递给下一个流程,要所有流程处理成功才算处理成功

简单的写法就是使用if else一直传递

使用责任链可以代码解耦,将每个流程处理封装一个类,处理完则传给下一个流程类,这样符合类的单一职责原则

我这里就是把if else给拆了出来,实现代码解耦,更有扩展性

应用

Javaweb中的 拦截器、过滤器中都采用的责任链
比如,对请求过滤会使用责任链将请求传给下一个处理器

还有一个预处理和后处理的方法
它们实现方式是递归

标签:Singleton,模式,instance,线程,private,单例,static,public
From: https://www.cnblogs.com/cheyaoyao/p/17779734.html

相关文章

  • 关于原始typescript实现todolist(装饰器模式)
    前言我是歌谣最好的种树是十年前其次是现在今天继续给大家带来的是原始typescript的讲解环境配置npminit-yyarnaddvite-D修改page.json配置端口{"name":"react_ts","version":"1.0.0","description":"","main":"index.......
  • 设计模式-原型模式
    原型模式(PrototypePattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的......
  • 互联网医院|互联网医疗模式走向实体建设阶段
    近年来,医疗服务领域新形态不断涌现,“互联网+医疗”作为其中突出的一种,在挂号结算、远程诊疗、咨询服务等方面进行了不少探索,而早在2015年全国互联网医院成立,标志着“互联网+医疗”模式已经从概念走向了实体建设阶段。 1、医院信息管理互联网医院系统需要管理医院的基本信息,如医院......
  • Redis 哨兵模式
    哨兵是一个分布式系统,你可以在一个架构中运行多个哨兵进程,这些进程使用流言协议来接收关于Master主服务器是否下线的信息,并使用投票协议来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master。一、哨兵模式概述1.1、为什么要启动哨兵模式当我们的主服务器宕机后,要手动的......
  • 设计模式04 —— 适配器模式
    设计模式04——适配器模式本教程参考:菜鸟教程-学的不仅是技术,更是梦想!(runoob.com)参考书:《图解设计模式》本系列为本人学习笔记,和课程学习笔记,资料和参考均源自互联网,希望各位大佬多多指点!介绍适配器模式(AdapterPattern)是作为两个不兼容的接口之间的桥梁。这种类型......
  • laravel:开启/关闭调试模式(10.27.0)
    一,文档地址:https://learnku.com/docs/laravel/10.x/configuration/14836#701998二,设置1,.env中关于调试的默认值:APP_DEBUG=true2,关闭调试APP_DEBUG=false说明:刘宏缔的架构森林—专注it技术的博客,网站:https://blog.imgtouch.com原文: https://blog.imgtouch.com/ind......
  • 【愚公系列】2023年10月 二十三种设计模式(十九)-观察者模式(Observer Pattern)
    ......
  • SpringMVC自定义处理返回值demo和异步处理模式DeferredResult demo
    搭建自定义返回值处理器demo新建springboot项目修改pom.xml<!--新增依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><gro......
  • C# switch 表达式 - 使用 switch 关键字的模式匹配表达式
    https://learn.microsoft.com/zh-cn/dotnet/csharp/language-reference/operators/switch-expression[DisplayName("空气质量指数颜色")]publicstringTvocColor=>AQIswitch{1=>"green",2=>......
  • redis其他操作、redis管道、django中使用redis、django缓存、celery介绍、补充单例
    redis其他操作'''delete(*names)exists(name)keys(pattern='*')expire(name,time)rename(src,dst)move(name,db))randomkey()type(name)'''#redis的key值,最大可以是多少?最大不超过512M一般1KB#redis的value值,最大可以是多少?最大不超过512M......