24种设计模式之单例模式(饿汉式、懒汉式)

这篇具有很好参考价值的文章主要介绍了24种设计模式之单例模式(饿汉式、懒汉式)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、单例模式

单例模式( Singleton Pattern )是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛,例如,总统,班主任等。J2EE标准中的ServletContext 、ServletContextConfig 等、Spring框架应用中的。

  • 特点:构造方法私有,提供一个全局访问点。

  • 实现方式:有很多,1.饿汉式 2.懒汉式 3.注册式 4.ThreadLocal

  • 优点:内存中只有一个实例,减少内存开销;避免对资源多重占用;设置全局访问点,严格控制访问。

  • 缺点:没有接口,扩展困难;如果要扩展单例对象,只有修改代码,没有其他途径,不符合程序的开闭原则。

单例模式懒汉和饿汉,Java面试及基础概念,java,Powered by 金山文档

二、饿汉式单例模式

饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象。它绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题。

总结:

final:防止反射破坏单例。

饿汉式缺点:可能会造成内存空间的浪费。

饿汉式单例模式适用于单例对象较少的情况。这样写可以保证绝对线程安全、执行效率比较高。但是它的缺点也很明显,就是所有对象类加载的时候就实例化。这样一来,如果系统中有大批量的单例对象存在,那系统初始化是就会导致大量的内存浪费。

饿汉式之单例实现方式

1、标准饿汉模式

通过私有构造器,防止外部进行实例创建;通过属性在类加载时实例化对象,提供全局访问方法取得实例。利用代码的执行先后顺序,在线程还没有出现前就完成了实例化。

public class HungrySingleton {
    // 静态实例代码段,饿汉实现类加载初始化时调用构造方法
    private static final HungrySingleton hungrySingleton = new HungrySingleton();
     
    // 私有方法防止外部调用创建对象
    private HungrySingleton() {}

    // 外部类获得单例对象方法
    public static HungrySingleton getInstance() {
        return hungrySingleton;
    }
}

该单例实现方式可以被反序列化和反射破坏:

(1)反射破坏方式如下:该方式可以通过构造方法创建出一个全新的实例对象。

public static void reflect() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        System.out.println(Test.getInstance());
        //反射破坏
        //得到类
        Class c = Test.class;
        Constructor<?> constructor = c.getDeclaredConstructor();
        //设置私有可调用
        constructor.setAccessible(true);
        // 打印创建的实例对象
        System.out.println(constructor.newInstance());

    }
单例模式懒汉和饿汉,Java面试及基础概念,java,Powered by 金山文档

可见该方法是通过调用构造方法创建出一个新的对象。

(2)反序列化破坏单例方式如下:

public static void ser() throws IOException, ClassNotFoundException {
        //反序列化
        ByteArrayOutputStream outputStream=new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(outputStream);
        //将类转化
        objectOutputStream.writeObject(Test.getInstance());
        System.out.println(Test.getInstance());
        ObjectInputStream objectInputStream=new ObjectInputStream(new ByteArrayInputStream(outputStream.toByteArray()));
        //读出类,变为一个新的类
        Test test= (Test) objectInputStream.readObject();
        System.out.println(test);
    }
单例模式懒汉和饿汉,Java面试及基础概念,java,Powered by 金山文档

该方式可以看出反序列化构造出的对象并不是通过构造方法。

由此针对上面两种破坏方式做出优化得到以下的代码:

public class Test implements Serializable {

    //静态实例代码段,饿汉实现类加载初始化时调用构造方法
    private static Test Instance=new Test();

    //私有方法防止外部调用创建对象
    private Test(){
        if(Instance!=null)// 此处方式反射调用破环单例对象,抛出异常
            throw new RuntimeException("单例模式不能创建");
        System.out.println("构造方法");
    }

    //外部类获得单例对象方法
    public static Test getInstance(){
        return Instance;
    }
    //其他方法
    public static void otherMethod(){
        System.out.println("other");
    }

    //防止反序列化破坏单例
    public Object readResolve(){
        return  Instance;
    }
}
2、静态代码块机制
public class HungryStaticSingleton {
    // 静态志方式饿汉式单例
    private static final HungryStaticSingleton hungrySingleton ;
    static {
        hungrySingleton = new HungryStaticSingleton();
    }
     
    /**
     * 私有构造
     */
    private HungryStaticSingleton() {}

    //取实例方法
    public static HungryStaticSingleton getInstance() {
        return hungrySingleton;
    }
}
3、枚举类实现饿汉:枚举类实现方式不会被反射和反序列化破环单例
public enum  Test_1 {
    Instance;

    //枚举类默认构造方法私有
    Test_1(){
        System.out.println("构造方法");
    }

    //获取对象
    public static Test_1 getInstance(){
        return Instance;
    }

    //其他方法
    public static void otherMethod(){
        System.out.println("other");
    }
}

三、懒汉式单例模式

懒汉式类被加载的时候,没有立刻被实例化,第一次调用getInstance的时候,才真正的实例化。

如果要是代码一整场都没有调用getInstance,此时实例化的过程也就被省略掉了,又称“延时加载”

一般认为“懒汉模式” 比 “饿汉模式”效率更高。

懒汉模式有很大的可能是“实例用不到”,此时就节省了实例化的开销。

懒汉式之单例实现方式

1、普通的懒汉式
public class LazySingleton {
    private LazySingleton() {
    }

    private volatile static LazySingleton instance;

    //加入了同步代码,解决线程不安全问题
    public synchronized static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

这种设计明显的一个问题就是执行效率低,无论是否已经存在实例,在多线程的情况下都会发生阻塞。

对以上代码进行改进,首先让当程序中实例存在的时候,直接返回实例,不需要抢占锁。当程序中不存在实例时,再抢占锁进行创建。根据以上的思想,出现了第二种懒汉式方式:

2、双重检查锁DCL(Double Check Lock双端检锁)
public class LazyDoubleCheckSingleton {
    private LazyDoubleCheckSingleton() {
    }

    private volatile static LazyDoubleCheckSingleton instance;

    public static LazyDoubleCheckSingleton getInstance() {
        //确定是否需要阻塞
        if (instance == null) {
            // 线程安全:双重检查锁(同步代码块)
            synchronized (LazyDoubleCheckSingleton.class) {
                //确定是否需要创建实例
                if (instance == null) {
                    //这里在多线程的情况下会出现指令重排的问题,所以对共有资源instance使用关键字volatile修饰
                    instance = new LazyDoubleCheckSingleton();
                }
            }
        }
        return instance;
    }

}

对于第二种方式,较第一种方式而言,性能提高了,但是代码的可读性差了。


DCL(Double Check Lock双端检锁)机制不一定线程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排。

原因在于某一个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能没有完成初始化

instance = new LazyDoubleCheckSingleton();可以分为以下3步完成(伪代码)

memory = allocate(); // 1.分配对象内存空间

instance(memory); // 2.初始化对象

instance=memory; // 3.设置instance指向刚分配的内存地址,此时instance != null

步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。

memory = allocate(); // 1.分配对象内存空间

instance=memory; // 3.设置instance指向刚分配的内存地址,此时instance != null,但是对象还没有初始化完成

instance(memory); // 2.初始化对象

但是指令重排只会保证串行语义的执行的一致性(单线程),但并不会关心多线程间的语义一致性。

所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,也就造成了线程安全问题。

3、静态内部类
 /**
 * 使用静态内部类,性能最优
 */
public class LazyInnerClassSingleton {
    //虽然构造方法私有了,但是逃不过反射的法眼
    private LazyInnerClassSingleton(){};
 
    // 懒汉式单例
    // LazyHoler里面的逻辑需等外部方法调用时候才执行
    // 巧妙运用了内部类的特性
    // JVM底层逻辑,完美避免了线程安全问题
    public static final LazyInnerClassSingleton getInstance(){
        return LazyHoler.LAZY;
    }
 
    public static class LazyHoler{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

为防止调用者反射破坏,可以这么写:

public class LazyInnerClassSingleton {
    //虽然构造方法私有了,但是逃不过反射的法眼
    private LazyInnerClassSingleton(){
        // 防止调用者反射攻击; 
        if(LazyHoler.LAZY != null){
            throw new RuntimeException("禁止创建多个实例!"); // 其他写法也可加上
        }
    };
 
    // 懒汉式单例
    // LazyHoler里面的逻辑需等外部方法调用时候才执行
    // 巧妙运用了内部类的特性
    // JVM底层逻辑,完美避免了线程安全问题
    public static final LazyInnerClassSingleton getInstance(){
        return LazyHoler.LAZY;
    }
 
    public static class LazyHoler{
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

分析:静态内部类相对来说更优,LazyHoler里面的逻辑需等外部方法调用时候才执行,所以也属于懒汉式,巧妙运用了内部类的特性,JVM底层逻辑,完美避免了线程安全问题,文章来源地址https://www.toymoban.com/news/detail-729021.html

到了这里,关于24种设计模式之单例模式(饿汉式、懒汉式)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java|多线程与高并发】设计模式-单例模式(饿汉式,懒汉式和静态内部类)

    设计模式是一种在软件开发中常用的解决复杂问题的方法论。它提供了一套经过验证的解决方案,用于解决特定类型问题的设计和实现。设计模式可以帮助开发人员提高代码的可重用性、可维护性和可扩展性。 设计模式有很多,本文主要介绍单例模式. 单例模式是一种创建型设

    2024年02月11日
    浏览(55)
  • 单例模式(懒汉式和饿汉式)

    单例模式是指保证某个类在整个软件系统中只有一个对象实例,并且该类仅提供一个返回其对象实例,由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。 在计算

    2024年02月13日
    浏览(50)
  • 单例模式:懒汉式和饿汉式

    目录 懒汉模式和饿汉模式 区别 示例 懒汉模式线程不安全 懒汉模式线程安全  懒汉模式内部静态变量线程安全 饿汉式线程安全 指的是在系统生命周期内,只产生一个实例。 分为 懒汉式 和 饿汉式 创建时机和线程安全 线程安全 :多线程共享资源的过程中,线程安全通过同

    2024年02月16日
    浏览(44)
  • 单例模式(饿汉式单例 VS 懒汉式单例)

    所谓的单例模式就是保证某个类在程序中只有一个对象 1.构造方法私有化(保证对象的产生个数)         创建类的对象,要通过构造方法产生对象        构造方法若是public权限,对于类的外部,可以随意创建对象,无法控制对象个数       构造方法私有化,这样类的外

    2024年02月09日
    浏览(42)
  • 【Java中23种设计模式-单例模式--饿汉式】

    加油,新时代打工人! 简单粗暴,直接上代码。 23种设计模式定义介绍 Java中23种设计模式-单例模式 Java中23种设计模式-单例模式2–懒汉式线程不安全 Java中23种设计模式-单例模式2–懒汉式2线程安全

    2024年02月22日
    浏览(49)
  • 自学设计模式(类图、设计原则、单例模式 - 饿汉/懒汉)

    设计模式需要用到面向对象的三大特性——封装、继承、多态(同名函数具有不同的状态) UML类图 eg.—— 描述类之间的关系(设计程序之间画类图)  +: public; #: protected; -: private; 下划线: static 属性名:类型(=默认值) 方法和变量分开------- 虚函数斜体,纯虚函数在虚函数类

    2024年02月11日
    浏览(41)
  • 【单例模式】饿汉式、懒汉式、静态内部类--简单例子

    单例模式是⼀个单例类在任何情况下都只存在⼀个实例,构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例,对外提供⼀个静态公有⽅法获取实例。 目录 一、单例模式 饿汉式  静态内部类 懒汉式 反射可以破坏单例 道高一尺魔高一丈 枚举 类⼀加载就创建对象,这

    2024年04月23日
    浏览(64)
  • 懒汉单例设计模式与饿汉单例设计模式

    单例模式即一个类确保只有一个对象,主要用于避免浪费内存 1 .饿汉单例设计模式 :拿到对象时,对象就早已经创建好了 写法: 把类的构造器私有 在类中自己创建一个对象,并赋值到一个变量 定义一个静态方法,返回自己创建的这个对象 2. 懒汉单例设计模式 :第一次拿到对象时

    2024年02月21日
    浏览(59)
  • 单例模式类设计|什么是饿汉模式和懒汉模式

    那么这里博主先安利一些干货满满的专栏了! 首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助。 高质量干货博客汇总 https://blog.csdn.net/yu_cblog/category_12379430.html?spm=1001.2014.3001.5482 一个类只能创建一个对象,

    2024年02月16日
    浏览(44)
  • 设计模式第一课-单例模式(懒汉模式和饿汉模式)

    个人理解:单例模式实际就是通过类加载的方式获取到一个对象,并且保证这个对象在使用中只有一个,不允许再次被创建 1、懒汉模式的基础写法 代码解释: (1)、编写LazySingleton类的时候,需要将成员属性设定为static,这样才会是类属性 (2)、重写构造方法,将其设置

    2024年02月05日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包