首页 > 其他分享 >设计模式-创建型-单例模式

设计模式-创建型-单例模式

时间:2023-09-01 13:44:17浏览次数:38  
标签:创建 private 懒汉 static 单例 new 设计模式 public

title: 设计模式-创建型-单例模式
keywords: 设计模式
cover: [https://s1.ax1x.com/2023/08/31/pP01Vit.png]
# sticky: 10
banner:  
  type: img
  bgurl: https://s1.ax1x.com/2023/08/31/pP01Vit.png
  bannerText: 设计模式-创建型-单例模式
categories: 设计模式
tags:
  - 设计模式
toc: false # 无需显示目录

单例模式

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。

单例模式是创建型模式,Spring框架中的ApplicationContext、J2EE中的ServletContext和ServletContextConfig、数据库的连接池都是单例模式

1、饿汉单例模式

饿汉单例模式在类加载的时候就立即初始化,并且创建单例对象。

绝对的线程安全,在线程还没有出现以前就实例化了,不可能存在访问安全问题。

  • 优点:没有任何加锁操作、执行效率比较高,用户体验比懒汉单例模式更好

  • 缺点:类加载的时候就初始化,不管用不用都占空间,浪费内存

1.1、饿汉单例案例

经典案例:

/**
 * TODO 懒汉单例模式
 *  饿汉单例模式在类加载的时候就立即初始化,并且创建单利对象
 *  绝对线程安全
 * @author ss_419
 * @version 1.0
 * @date 2023/9/1 09:10
 */
public class HungrySingleton {
    // 先静态,后动态
    // 先属性,后方法
    // 先上后下

    private static final HungrySingleton HUNGRY_SINGLETON = new HungrySingleton();

    private HungrySingleton() {}
    // 通过getInstance返回对象实例
    public static HungrySingleton getInstance(){
        return HUNGRY_SINGLETON;
    }
}

改进写法,使用静态代码块机制:

/**
 * TODO 静态饿汉单例模式
 * 利用静态代码块的机制
 * 饿汉单例模式适用于单例对象较少的情况
 * @author ss_419
 * @version 1.0
 * @date 2023/9/1 09:16
 */
public class HungryStaticSingleton {
    private static final HungryStaticSingleton INSTANCE ;

    /**
     * 通过静态代码块对类进行实例化
     */
    static {
        // 静态代码块,在初始化之前的连接阶段中的准备阶段就对静态变量分配内存、设置初始值等操作
        INSTANCE = new HungryStaticSingleton();
    }

    /**
     * 不能通过构造器对类进行实例化操作
     */
    private HungryStaticSingleton() {
    }
    // 提供一个全局访问点,以供实例化对象
    public static HungryStaticSingleton getInstance() {
        return INSTANCE;
    }
}

2、懒汉单例模式

特点:被外部类调用的时候内部类才会加载

2.1、懒汉单例案例

懒汉单例模式在外部需要使用的时候才进行实例化:

/**
 * TODO 懒汉单例模式
 * 懒汉单例模式:在外部需要使用的时候才进行实例化
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/9/1 09:20
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton() {

    }

    // 静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;

    // 提供全局访问点,实例化单例对象
    public static LazySimpleSingleton getInstance() {
        if (lazy == null){
            // 对象没有创建的时候才new,否则就返回之前所存在的对象
            return new LazySimpleSingleton();
        }
        return lazy;
    }
}

创建一个线程类ExectorThread:

public class ExectorThread implements Runnable{
    @Override
    public void run() {
        // 通过懒汉单例中提供的全局访问点创建一个单例对象
        LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();
        // 查看线程中存在的对象
        System.out.println(Thread.currentThread().getName() + ": " + singleton);
    }
}

测试代码:

    @Test
    public void test1(){
        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        // 这里测试,出现两个类不同的情况,单例存在线程安全 问题
        t1.start();
        t2.start();

        System.out.println("End");
    }

通过测试,上面的代码存在线程安全问题,怎么样才能使线程安全呢?

第一个方法:通过对获得实例方法进行加锁操作,保证当前线程获得实例时,其他线程都处于阻塞状态

 package org.pp.my.design_pattern.create.singleton2.lazy;

/**
 * TODO 懒汉单例模式
 * 懒汉单例模式:在外部需要使用的时候才进行实例化
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/9/1 09:20
 */
public class LazySimpleSingleton {
    private LazySimpleSingleton() {

    }

    // 静态块,公共内存区域
    private static LazySimpleSingleton lazy = null;

    /**
     * 为了保证懒汉单例模式在多线程环境下线程安全,通过synchronized加锁实现
     * @return
     */
    public synchronized static LazySimpleSingleton getInstance() {
        if (lazy == null){
            // 对象没有创建的时候才new,否则就返回之前所存在的对象
            return new LazySimpleSingleton();
        }
        return lazy;
    }
}

但是通过synchronized加锁的方式,会使性能降低

使用双重锁既能兼顾线程安全又能提升程序性能:

/**
 * TODO 懒汉模式双重检查锁
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/9/1 09:34
 */
public class LazyDoubleCheckSingleton {
    private volatile static LazyDoubleCheckSingleton lazy = null;

    private LazyDoubleCheckSingleton(){

    }
    public static LazyDoubleCheckSingleton getInstance() {
        if (lazy == null){
            synchronized (LazyDoubleCheckSingleton.class){
                lazy = new  LazyDoubleCheckSingleton();
                // 1、分配内存给这个对象
                // 2、初始化对象
                // 3、设置lazy指向刚分配的内存地址
            }
        }
        return lazy;
    }

}

采用静态内部类方式,避免加锁操作

/**
 * TODO 懒汉模式-静态内部类
 * 通过静态内部类来避免使用synchronized加锁的情况
 * @author ss_419
 * @version 1.0
 * @date 2023/9/1 09:39
 */
public class LazyInnerClassSingleton {
    // 使用LazyInnerClassSingleton,默认会先初始化内部类
    // 如果没使用,则内部类是不加载的
    private LazyInnerClassSingleton(){

    }
    // 每一个关键字都不是多余的,static是为了使单例的空间共享,保证这个方法不会被重写、重载
    public static final LazyInnerClassSingleton getInstance(){
        return LazyHolder.LAZY;
    }

    private static class LazyHolder{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

2.2、反射破坏单例

通过反射机制,获取类的私有构造方法,强制访问

    /**
     * 通过反射来破坏单例
     */
    @Test
    public void test2(){
        try {
            // 反射获取单例类
            Class<LazyInnerClassSingleton> clazz = LazyInnerClassSingleton.class;
            // 通过反射获取私有的构造方法
            Constructor<LazyInnerClassSingleton> c = clazz.getDeclaredConstructor(null);
            // 强制访问
            c.setAccessible(true);
            // 暴力初始化
            Object o1 = c.newInstance();
            // 调用了两次构造方法,相当于“new”了两次,犯了原则性错误
            Object o2 = c.newInstance();
            System.out.println("o1 = " + o1);
            System.out.println("o2 = " + o2);
            System.out.println(o1 == o2);
        }catch (Exception e){

        }
    }

为了避免上述情况发生,在构造器中做一些操作,一旦出现多次重复创建,则直接抛出异常

/**
 * TODO 史上最强的懒汉单例模式
 * 一旦出现多次重复创建,则直接抛出异常
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/9/1 09:51
 */
public class LazyInnerClassNoNullSingleton {
    // 使用LazyInnerClassGeneral的时候,默认会先初始化内部类
    // 如果没有使用,则内部类是不加载的
    private LazyInnerClassNoNullSingleton(){
        if (LazyHolder.LAZY != null){
            // 不为null,说明对象已经实例化过了
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    /**
     * 每一个关键字都不是多余的
     * static是为了单例的空间共享,保证这个方法不会被重写、重载
     * @return
     */
    public static LazyInnerClassNoNullSingleton getInstance(){
        // 在返回之前,一定会先加载内部类
        return LazyHolder.LAZY;
    }

    /**
     * 默认不加载
     */
    private static class LazyHolder {
        private static final LazyInnerClassNoNullSingleton LAZY = new LazyInnerClassNoNullSingleton();
    }
}

标签:创建,private,懒汉,static,单例,new,设计模式,public
From: https://www.cnblogs.com/atwood-pan/p/17671654.html

相关文章

  • 达梦DM8手动创建AWR报告
    达梦数据库AWR报告创建方式如下:1、启用系统包和AWR包:SQL>CALLSP_INIT_AWR_SYS(1);DMSQL过程已成功完成已用时间:00:00:01.380.执行号:59500.SQL>CALLSP_CREATE_SYSTEM_PACKAGES(1);DMSQL过程已成功完成已用时间:00:00:03.403.执行号:59501.2、查询AWR......
  • mysql 创建只读权限账号
    命令行登录mysql-uroot-p创建只读权限的账号【将<username>替换为用户名, <password>替换为密码。'%' 表示该账号可以从任何主机连接。如果希望限制连接的主机,可以将 '%' 替换为具体的主机名或IP地址。】CREATEUSER'<username>'@'%'IDENTIFIEDBY'<password>......
  • windows10创建conda环境失败:CondaHTTPError: HTTP 000 CONNECTION FAILED for url <htt
    问题描述创建新环境时,报错,创建不成功Collectingpackagemetadata(current_repodata.json):doneSolvingenvironment:doneCondaHTTPError:HTTP000CONNECTIONFAILEDforurl<https://conda.anaconda.org/conda-forge/linux-64/current_repodata.json>Elapsed:-AnHTTP......
  • 23种设计模式之代理模式
    代理设计模式(ProxyDesignPattern)是一种结构型设计模式,它为其他对象提供一个代理,以控制对这个对象的访问。代理模式可以用于实现懒加载、安全访问控制、日志记录等功能。在设计模式中,代理模式可以分为静态代理和动态代理。静态代理是指代理类在编译时就已经确定,而动态代理是指代理......
  • 23种设计模式之建造者模式
    Builder模式,也叫生成器模式。创建者模式主要包含以下四个角色:产品(Product):表示将要被构建的复杂对象。抽象创建者(AbstractBuilder):定义构建产品的接口,通常包含创建和获取产品的方法。具体创建者(ConcreteBuilder):实现抽象创建者定义的接口,为产品的各个部分提供具体实现。指挥者(Direc......
  • 通过动态创建a标签,循环批量下载文件所遇到的问题记录
    1.现象:直接for循环动态创建a标签后,进行click事件触发下载时,你会发现浏览器只下载了最后一个文件原因:浏览器下载时,太快的话,会取消上次的下载解决方法一:可添加settimeout定时器,进行一定时间延迟,比如300毫秒,把下载触发的事件放到定时器中即可。2.解决方法二通过ifram......
  • 23种设计模式之工厂模式
    工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。简单工厂(SimpleFactory):简单工厂叫作静态工厂方法模式(StaticFactoryMethodPattern)。假设一个场景,需要一个资源加载器,要根据不同的url进行资源加载,但是如果将所有的加载实现代码全部封装在了一个load方法中,就会导致......
  • 23种设计模式之单例模式
    单例设计模式(SingletonDesignPattern):一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式。如何实现一个单例:常见的单例设计模式,有如下五种写法,在编写单例代码的时候要注意以下几点:1、构造器需要私有化2、暴露一个公共的获取单例对象的接口3......
  • 使用samba创建共享文件夹(Linux - Windows)
    1.安装samba有些Linux已经自带了samba$sudoaptinstallsamba-y2.配置防火墙详情请参考https://zhuanlan.zhihu.com/p/508580900,因本人使用的是MX23,不是很会设置,且无其它安全需求,故直接关闭防火墙3.配置samba$sudovim/etc/samba/smb.conf#按个人需要可以备份......
  • 微信开发之一键创建标签的技术实现
    简要描述:添加标签请求URL:http://域名地址/addContactLabel请求方式:POST请求头Headers:Content-Type:application/jsonAuthorization:login接口返回参数:参数名必选类型说明wId是String登录实例标识labelName是String标签名称请求参数示例{......