首页 > 其他分享 >常用设计模式-单例模式

常用设计模式-单例模式

时间:2024-04-26 12:55:47浏览次数:20  
标签:常用 Singleton 线程 单例 static singleton 设计模式 public

单例模式 (保证只有一个实例,并只提供一个访问实例的访问点)

单例模式的创建方式:

  • 饿汉模式-静态变量
package com.pattern;

//饿汉模式-静态变量
public class Singleton {
    
    // 类初始化时,会立即加载该对象,线程天生安全,调用效率高
    private static final Singleton singleton = new Singleton();
    
    private Singleton() {
        System.out.println("私有Singleton构造参数初始化");
    }
    
    public static Singleton getInstance() {
        return singleton;
    }
}

由于使用了static关键字,保证了在引用这个变量时,关于这个变量的所有写入操作已经完成,所以保证了JVM层面的线程安全。

  • 饿汉模式-静态代码块

 

package com.pattern;

//饿汉模式-静态代码块
public class Singleton {
    
    private static  Singleton singleton;
    
    private Singleton() {
        System.out.println("私有Singleton构造参数初始化");
    }
    
    static {
        try {
            //Do something ... //new 放在static代码块里可初始化一些变量或者读取一些配置文件等
             singleton = new Singleton()
        }catch (Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }
    public static Singleton getInstance() {
        return singleton;
    }
}
  • 懒汉式-单线程
package com.singleton;

//懒汉式-单线程
public class Singleton {
    
    //类初始化时,不会初始化该对象,真正需要使用的时候才会创建该对象,具备懒加载功能
    private static Singleton singleton;

    private Singleton() {
        System.out.println("私有Singleton构造参数初始化");
    }
    
    public  static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

只适用于单线程场景,多线程情况下可能发生线程安全问题,导致创建不同实例的情况发生。如果是多线程同时调用getInstance(),会有并发问题啊,多个线程可能同时拿到instance == null的判断,这样就会重复实例化,单例就不是单例。

解决见下面

  • 懒汉式-synchronized
package com.singleton;

//懒汉式-synchronized
public class Singleton {
    
    private static Singleton singleton;

    private Singleton() {
        System.out.println("私有Singleton构造参数初始化");
    }
    //synchronized 保证了同步访问该方法,严格串行制,性能降低
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
  • 懒汉式-Double check-volatile
package com.singleton;

//懒汉式-Double check-volatile
public class Singleton {
    
    private static volatile Singleton singleton;

    private Singleton() {
        System.out.println("私有Singleton构造参数初始化");
    }
    //该方式通过缩小同步范围提高访问性能,同步代码块控制并发创建实例。采用双重检验(内外两个判空)
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

内层的判空的作用:

当两个线程同时执行第一个判空时,都满足的情况下,都会进来,然后去争锁,假设线程1拿到了锁,执行同步代码块的内容,创建了实例并返回,释放锁,然后线程2获得锁,执行同步代码块内的代码,因为此时线程1已经创建了,所以线程2虽然拿到锁了,如果内部不加判空的话,线程2会再new一次,导致两个线程获得的不是同一个实例。线程安全的控制其实是内部判空在起作用。

可以只加内层判空是ok的

外层的判空的作用:

  • 内层判空已经可以满足线程安全了,加外层判空的目的是为了提高效率。
  • 因为可能存在这样的情况:如果不加外层判空,线程1拿到锁后执行同步代码块,在new之后,还没有释放锁的时候,线程2过来了,它在等待锁(此时线程1已经创建了实例,只不过还没释放锁,线程2就来了),然后线程1释放锁后,线程2拿到锁,进入同步代码块中,判空不成立,直接返回实例。
  • 这种情况线程2是不是不用去等待锁了?因为线程1已经创建了实例,只不过还没释放锁。
  • 所以在外层又加了一个判空就是为了防止这种情况,线程2过来后先判空,不为空就不用去等待锁了,这样提高了效率。

volatile作用:

  • 在多线程的情况下,双重检查锁模式可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作,因为new那行代码并不是一个原子指令,会被分割成多个指令。
  实例化对象实际上可以分解成以下4个步骤:
    1. 为对象分配内存空间
    2. 初始化默认值(区别于构造器方法的初始化)
    3. 执行构造器方法
    4. 将对象指向刚分配的内存空间
  编译器或处理器为了性能的原因,可能会将第3步和第4步进行重排序:
    1. 为对象分配内存空间
    2. 初始化默认值
    3. 将对象指向刚分配的内存空间
    4. 执行构造器方法线程可能获得一个初始化未完成的对象

 

  • 静态内部类
package com.singleton;

// 静态内部类方式
public class Singleton {
    
    //结合了懒汉式和饿汉式各自的优点,真正需要对象的时候才会加载,加载类是线程安全的。
    private Singleton() {
        System.out.println("私有Singleton构造参数初始化");
    }
    
    public static class SingletonClassInstance {
        private static final Singleton singleton = new Singleton();
    }
    
    // 方法没有同步
    public static Singleton getInstance() {
        return Singleton.singleton;
    }
}

该方式是线程安全的,适用于多线程,利用了java内部类的特性:

  静态内部类不会自动随着外部类的加载和初始化而初始化,内部类是要单独加载和初始化的。此方式单例对象是在内部类加载和初始化时才创建的,因此它是线程安全的,且实现了延迟初始化

 

  • 枚举单例式

枚举是最简洁的,不需要考虑构造方法私有化。

值得注意的是枚举类不允许被继承,因为枚举类编译后默认为final class,可防止被子类修改。

。枚举类是利用JVM类加载机制来保证线程安全的(细节见这篇),并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

package com.singleton;

// getInstance()访问性能高,线程安全    非延迟初始化
public enum EnumSingleton {

    INSTANCE;
    
    private Resource instance;
    
    EnumSingleton(){
    	//doSomething();
        instance = new Resource();
    }
    
    public Resource getInstance() {
        return instance;
    }
    
}

public class EnumSingletonEnumTest {
    public static void main(String[] args) {
        Resource instance = EnumSingleton.INSTANCE.getInstance();
        System.out.println(instance);
    }
}

破坏单例模式的方法和防范措施

反射是通过强行调用私有构造方法生成新的对象。

防范方法

如果我们想要阻止单例破坏,可以在构造方法中进行判断,若已有实例,则阻止生成新的实例。

private Singleton(){
    if (instance != null){
        throw new RuntimeException("实例已经存在,请通过 getInstance()方法正确获取");
    }
}

序列化和单例模式

有时在分布式系统中,我们需要在单例类中实现可序列化接口,这样我们就可以在文件系统中存储它的状态,并在以后的时间点检索它。

每当反序列化它时,它都会创建该类的新实例。对比其hashCode值不一致

防范方法
  1. 不实现序列化接口
  2. 如果必须实现序列化接口,可以重写反序列化方法readResolve(),反序列化时直接返回相关单例对象。
protected Object readResolve() {
    return getInstance();
}

cloneable接口的破坏

和可序列化接口有些类似,当需要实现单例的类允许clone()时,如果处理不当,也会导致程序中出现不止一个实例。 

防范方法
重写clone()方法,调clone()时直接返回已经实例的对象。
protected Object clone() throws CloneNotSupportedException {
        return instance;
}

 

标签:常用,Singleton,线程,单例,static,singleton,设计模式,public
From: https://www.cnblogs.com/klyjb/p/18159803

相关文章

  • 常用的时间序列分析方法总结和代码示例
    时间序列是最流行的数据类型之一。视频,图像,像素,信号,任何有时间成分的东西都可以转化为时间序列。在本文中将在分析时间序列时使用的常见的处理方法。这些方法可以帮助你获得有关数据本身的见解,为建模做好准备并且可以得出一些初步结论。我们将分析一个气象时间序列。利用逐时ERA......
  • C#中常用作event的delegate 函数
    1publicdelegatevoidEventHandler<TEventArgs>(objectsender,TEventArgse);使用例子:publiceventEventHandler<TcpClientKickedEventArgs>ClientKicked;//怎样调用if(ClientKicked!=null){ClientKicked(this,newTcpClientKickedE......
  • MinIO 常用 API 快速入门
    快速入门minio中文网minio官网minio有开源版和收费版,使用开源版时,若修改了minio的源代码,需要将修改后的源代码完全公开。启动miniominio文档提供了多个运行环境的安装流程,此处以windows为例,其它运行环境文档上都有介绍。相关文档下载minio.exe:https://dl.minio......
  • Matlab常用语句
    clear %用于清除MATLAB工作空间中的所有变量close %用于关闭所有图形窗口clc %用于清空命令窗口的文本内容。gridon;%打开网格线//------------------------分隔符------------------------heaviside(t)%生成单位阶跃函数rectpuls %生成矩形脉冲信号的函数......
  • React 《常用库》
    lodashLodash通过降低array、number、objects、string等等的使用难度从而让JavaScript变得更简单。Lodash的模块化方法非常适用于:遍历array、object和string对值进行操作和检测创建符合功能的函数官网https://www.lodashjs.com/#installnpmi--savelodash......
  • echart 常用属性
    echart常用属性基础属性title左上角标题legend每一项的列表xAxis:x轴上的数据yAxis:y轴上的数据提示框tooltip:{trigger:'axis'},demo地址:https://echarts.apache.org/v4/examples/zh/editor.html?c=line-stack文字转动斜着摆放axisLabel.rota......
  • 记录一次责任链设计模式使用低级错误
    记录一次责任链设计模式使用低级错误目录记录一次责任链设计模式使用低级错误背景流程发现问题解决方案总结背景提供一个服务支持语音转写成文本,以及历史转写备份数据的简单服务。提供一个接口批量上传,一次最大1000条(分表)落库之后同时发送到消息队列并更新数据状态消费......
  • C#实现单例模式的几种方法
    C#实现单例模式的几种方法    C#中readonly的理解与使用   Readonly(C#参考)介绍单例模式是软件工程学中最富盛名的设计模式之一。从本质上看,单例模式只允许被其自身实例化一次,且向外部提供了一个访问该实例的接口。通常来说,单例对象进行实例化时一般不带参数,因为......
  • chsime.exe 是 Windows 系统中的一个文件,它是中文输入法编辑器的主要执行文件。这个文
    chsime.exe是Windows系统中的一个文件,它是中文输入法编辑器的主要执行文件。这个文件通常用于启动和管理中文输入法,使用户能够在Windows系统中输入中文字符。通常情况下,当用户需要在Windows系统中输入中文时,他们可以通过点击任务栏上的语言栏或使用快捷键切换到中文输......
  • CentOS 常用命令详解
    前言CentOS是一种基于Linux内核的开源操作系统,广泛应用于服务器环境和个人电脑中。在使用CentOS进行系统管理和维护时,掌握一些常用的命令是至关重要的。本文将介绍一些在CentOS中经常使用的命令,帮助你更好地管理和操作系统。ls-列出文件和目录ls命令用于列出当前......