JavaEE:单例模式(饿汉模式和懒汉模式)精讲

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

前言

什么是单例模式?

其实用通俗的话就是程序猿约定俗成的一些东西,就比如如果你继承了一个抽象类,你就要重写里面的抽象方法,如果你实现了一个接口,你就要重写里面的方法。如果不进行重写,那么编译器就会报错。这其实就是一个规范。

而单例模式能保证某个类在程序中只存在唯一的一个实例,而不会创建出多个实例

那么,单例模式又分成“饿汉”和“懒汉”两种、

一.饿汉模式 

顾名思义,饿汉模式就是在类加载的时候,创建实例。

package thread;
//期待这个类能有唯一实例
public class hungryDemo {
    private static hungryDemo instance = new hungryDemo();

    public static hungryDemo getInstance() {
        return instance;
    }

    //把构造方法设置为私有,这样在类外就无法 new 出这个对象的实例了
    private hungryDemo() {

    }
}

代码解读:

1. 首先创建了一个 hungryDemo 类,里面有一个类方法和一个类变量

2. 我们将构造方法设置为了private,那么在类外就无法再针对 hungryDemo 再实例化类了

JavaEE:单例模式(饿汉模式和懒汉模式)精讲,java,开发语言


我们现在在类外,通过 hungryDemo提供的  public static hungryDemo getInstance 方法来进行调用,可以发现如下结果:

class Demo1 {
    public static void main(String[] args) {
        hungryDemo h1 = hungryDemo.getInstance();
        hungryDemo h2 = hungryDemo.getInstance();
        System.out.println(h1 == h2);

    }
}

运行结果:

JavaEE:单例模式(饿汉模式和懒汉模式)精讲,java,开发语言

可以发现,两者获取到的类对象引用是一致的,那么单例模式的饿汉版本就创建好了。

二.懒汉模式 

🎈单线程版本

我们的预期结果是不变的,那就是要实现单例模式,也就是这个类 只能被实例化一次!!!

那么懒汉模式顾名思义,也就是类加载的时候不创建实例,第一次使用的时候才创建实例。

那么我们可以写出以下代码:

package thread;

public class lazyDemo {
    private static lazyDemo instance = null;  
    public static lazyDemo getInstance() {
        /**
         * 只有调用该方法的时候,才创建对象
         */
                if (instance == null) {
                    instance = new lazyDemo();
                }               
        return instance;
    }
    private lazyDemo() {

    }
}

代码解读:

🍺首先,设置类成员变量 instance 为 null,当第一次使用getInstance()的时候才进行创建对      象。

🍺其次,跟饿汉模式一样,将类的构造方法设置为 private ,类外无法再次创建对象。

🍺最后,在getInstance方法中判断 instance 是否为空,为空那就创建对象。不为空说明已经        被调用一次了,那么就直接返回 instance 引用。

🎈🎈多线程版本 1

在以上的单线程版本中,我们不难发现以下问题:

假设现在有两个线程,他们是按照如下的顺序来执行的:

JavaEE:单例模式(饿汉模式和懒汉模式)精讲,java,开发语言

那么此时的代码就会出现问题: t1 线程首先判断了 instance 是否为空,此时 t2 线程来运行了,也判断 instance 是否为空,紧接着 instance不为空,然后就创建了对象! 然后再回到 t1 线程中,又要进行创建对象。  此时问题已经很明显了,那就是 由于if代码块在多线程中的执行顺序问题导致的

更精简一下:

就是 instance = new lazyDemo() 是写操作, instance == null 是读操作,在多线程中,如果一段代码即涉及读操作,又设计写操作,那么就很容易出现问题!!!


🍭解决办法:

  当一段代码是因为读写操作出BUG,我们首先想到的就是加锁。也就是在我写的时候,你不要       读。我读的时候,你不要写。

synchronized 是一种内置的 Java 关键字,它用于实现线程的同步。当一个线程进入synchronized块或方法时,它获得了锁,这会阻止其他线程同时进入相同的synchronized块或方法,从而确保了共享资源的互斥访问。

修改代码如下:

package thread;

public class lazyDemo {
    private static lazyDemo instance = null;  
    public static lazyDemo getInstance() {
        /**
         * 只有调用该方法的时候,才创建对象
         */
            synchronized (lazyDemo.class) {   //1. 加锁解决的是线程安全问题(确保是单例模式,只new一次)
                if (instance == null) {
                    instance = new lazyDemo();
                }
            }
        return instance;
    }

    private lazyDemo() {

    }
} 

对于对象lazyDemo.class,实际上就是lazyDemo这个类,也就是对类进行加锁。

此时加锁之后,当t1线程进行读写操作的时候,t2线程再次进行访问就只能进行阻塞。

此时t1就可以放心创建出一个对象出来,此时t2再进行调用方法的时候,instance 不为空,就直接返回 t1 创建好的对象引用。 这时候就确保了只创建出一个实例。

🎈🎈🎈 多线程版本2

其实,多线程版本1 还是有问题的,我们发现:如果t1 线程加锁后创建好了对象,其他线程(t2,t3,t4.........)在进行访问的时候,首先就要进行加锁操作。 也就是每次访问都要进行加锁,这是一个资源开销非常大的操作。

深入探究一下,我们发现其他线程(t2,t3,t4.........)在进行访问的时候,只需要判断当前的对象是否被创建好了即可。如果被创建好了,那么就直接返回对象引用。如果没有被创建好,再进行加锁创建对象。

修改代码如下:

public class lazyDemo {
    private static lazyDemo instance = null; 
    public static lazyDemo getInstance() {
        /**
         * 只有调用该方法的时候,才创建对象
         */
        if(instance == null) {   //2. if判断解决的是多次加锁,加锁频率太高的问题
            synchronized (lazyDemo.class) {   //1. 加锁解决的是线程安全问题(确保是单例模式,只new一次)
                if (instance == null) {
                    instance = new lazyDemo();
                }
            }
        }
        return instance;
    }

    private lazyDemo() {

    }
}

在多线程中,这两个 if 的作用大不相同!!!

修改后,我们发现如果 t1 线程创建好了对象, 此时其他线程(t2,t3,t4.........)在进行调用的时候,首先判断了instance 是否为空,不为空就说明已经创建好了对象~

🎈🎈🎈🎈多线程版本3

其实到现在,这个懒汉模式的单例代码还是有问题!!!

 在多线程下,要考虑到编译器的优化问题,当编译器没有按照我们的逻辑进行操作的时候,那么就会出现问题。

在此代码中,new 操作可以分为以下三步:

1.申请内存空间(一定先执行),获取到内存地址  

2.在内存空间上构造对象(构造方法)

3.把内存的地址,赋值给 instance 引用

在单线程环境下,执行那种顺序都无所谓,但是如果在多线程环境下,就可能出现问题:

假设是按照 1 3 2 的顺序来执行,当 1 和 3 操作执行完的时候,instance 已经非空了,只是内存空间上还没有构造对象 / 方法,此时instance 指向的是一个还没初始化的非法对象。 此时此刻 t2 进行访问,判断 instanc 是不为空的,然后就返回了一个还没初始化的非法对象,进一步 t2 线程就有可能访问 instance 里面的属性和方法。此时就出现了问题了。

这个问题就是指令重排序问题,解决办法就是让 instance 加入上volatile 关键字,此时就避免了指令重排序问题。

    //3.加 volatile是为了解决new 操作的指令重排序问题    
private volatile static lazyDemo instance = null; 

此时的代码就会严格按照 1  2  3 的顺序执行。


总结:单例模式是一个约定俗成的规范,保证一个类只能实例化一个对象。饿汉模式在多线程和单线程都没有问题,因为一开始它就创建好了对象。 而懒汉模式的多线程版本会出现以下三个问题:1. 线程安全问题( 确保只new 一次)2. 多次重复加锁的问题  3. 指令重排序问题。

希望以上的解决办法对你有所帮助!!!文章来源地址https://www.toymoban.com/news/detail-757734.html

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

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

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

相关文章

  • Java设计模式之单例模式详解(懒汉式和饿汉式)

    在开发工作中,有些类只需要存在一个实例,这时就可以使用单例模式。Java中的单例模式是一种常见的设计模式,它确保一个类只有一个实例,并提供全局访问点。下面来介绍一下两种常见的单例模式:懒汉式和饿汉式。 懒汉式属于一种延迟加载的单例模式,它的特点是在

    2024年02月15日
    浏览(35)
  • 【Java|多线程与高并发】设计模式-单例模式(饿汉式,懒汉式和静态内部类)

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

    2024年02月11日
    浏览(43)
  • 【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列

    目录 1、单例模式 1.1、饿汉模式 2.1、懒汉模式  2、阻塞队列 2.1、BlockingQueue 阻塞队列数据结构 对框架和设计模式的简单理解就是,这两者都是“大佬”设计出来的,让即使是一个代码写的不太好的“菜鸡程序员”也能写出还可以的代码。 设计模式也可以认为是对编程语言语

    2024年03月23日
    浏览(82)
  • Java单例模式的五种实现方式 懒汉式 饿汉式 双重校验锁 静态变量 静态内部类 枚举实现单例模式等

    Java单例模式是一种设计模式,用于确保一个类只有一个实例,并提供全局访问点以获取该实例。它通常用于需要共享资源或控制某些共享状态的情况下。 懒汉式:在类加载的时候就创建对象,要再调用方法时才创建对象,减少内存开销。 饿汉式:再类加载的时候就实例化对

    2024年04月27日
    浏览(26)
  • 【单例模式】饿汉模式和懒汉模式的单例模式

    设计模式是一种 在软件设计中经过验证的解决问题的方案或者模版 。它们是从实践中总结出来的,可以帮助解决常见的设计问题,提高代码的重用性、维护性和扩展性。 设计模式可以分为三大类: 创建型模式(Creational Patterns) :创建型模式关注对象的实例化过程,包括如

    2024年02月16日
    浏览(55)
  • 设计模式之单例模式(懒汉, 饿汉)

    单例模式是一种常用的软件设计模式, 该模式的主要目的是确保某一个类在内存中只能有一个实例对象, 通过单例模式的方法创建的类在当前进程中只有一个实例对象. 常见的单例模式有两种: 饿汉式, 这里的 “饿” 意义表述不够清晰, 用 “急” 来表述意义更加容易联想一些

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

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

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

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

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

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

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

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

    2024年02月11日
    浏览(27)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包