当前位置: 首页 > 编程笔记 >

java 单例模式容易忽略的细节

鲁文昌
2023-03-14
本文向大家介绍java 单例模式容易忽略的细节,包括了java 单例模式容易忽略的细节的使用技巧和注意事项,需要的朋友参考一下

java单例模式

直接讲实现单例模式的两种方法:懒汉式和饿汉式,单例模式的概念自己上网搜吧这里就不讲了!

这里会涉及到java中的jvm,如果你没有这方面的知识,我建议你先去补补,不然会有点迷糊!

首先说说类什么时候进行加载?

java虚拟机没有进行强制性的约束,但是对于初始化却严格规定了有且只有4种情况必须先对类进行初始化。

我们要知道的是在类加载的过程中,加载、验证、准备是在初始化之前完成的,所以进行了初始化,加载、验证、准备自然就在之前完成了。

然后这四种情况是分别遇到 new 、 getstatic 、 putstatic 和 invokestatic 这四条指令时,如果对应的类没有初始化,则要对对应的类先进行初始化。

讲完类加载时机,就可以讲懒汉式和饿汉式了。

直接先说说懒汉式为什么是线程不安全的?

先看最开始的代码:

public class Student2 {

  //1:构造私有
  private Student2(){}
  //2:定义私有静态成员变量,先不初始化
  private static Student2 student = null;

  //3:定义公开静态方法,获取本身对象
  public static Student2 getSingletonInstance(){
    //没有对象,再去创建
    if (student == null) {
      student = new Student2();
    }
    //有对象就返回已有对象
    return student;
  }  
}

结合之前讲的类加载内容,遇到new或加载静态方法了就会进行类加载了。

线程1它new了一个对象,线程2它紧接着也new一个对象,第二个对象的值把第一个对象的值覆盖了,不管new了多少个对象,都会产生垃圾对象,只有最后一个对象才会保持住,其他对象都会变成不可达对象,被垃圾回收,这个过程就相当于产生了大量无效对象,这就是线程不安全的原因!

那为了让懒汉式变得线程安全,我们要怎么做?

看代码:

public class Student4 {

  private volatile static Student4 student = null;
  private Student4() {}

  public static Student4 getSingletonInstance() {
    if (student == null) {//第一个null判断,是先大范围过滤一遍
      synchronized (Student4.class) {
        if (student == null) {
          student = new Student4();
        }
      }
    }
    return student;
  }
}

这个叫双重检查锁DCL,第一个if先大范围判断是不是空值,经过synchronized,线程1先进去执行完后,线程2才能进去,然后第二个if判断是否完成创建类的实例,线程1创建完了,线程2就不用创建了。

那为什么要加volatile关键字呢?

因为我们Student student = new Student()的执行过程是:

1、new触发类加载机制(已经被加载过的类不需要再次加载)

2、分配内存空间

3、将对象进行初始化4、讲对象引用地址赋值给栈空间中的变量但我们JVM中的JIT即时编辑器会对代码的执行过程进行优化,把过程变为1、2、4、3。

这是什么意思呢?就是未经初始化直接赋值,这样就是student直接有值了,但整个对象还未初始化完成,所以这个对象是不完整的,是个未成品。在JVM规范中,它是一个根本不能用的对象。

到了这个时候,线程1做了这么多事,我们让它休息会,给CPU稍微停一下,线程2就来了,它就直接得到了对象,但它调用对象的方法时,就会报错。虽然这个对象有值,但还未初始化完成。所以我们要加上volatile关键字禁止指令重新排序。

面试重灾区说的差不多了,饿汉式还是要讲讲。

最后就说说饿汉式为什么没有线程安全问题?​

看代码:

public class Student1 {
  // 2:成员变量初始化本身对象
  private static Student1 student = new Student1();
  // 构造私有
  private Student1() {
  }
  // 3:对外提供公共方法获取对象
  public static Student1 getSingletonInstance() {
    return student;
  }
  public void sayHello(String name) {
    System.out.println("hello," + name);
  }
}

根据类加载的东西,在多线程的条件下,线程1先执行getSingletonInstance()时,就会进行类加载,类的静态资源就会进行初始化。根据JVM安全机制里说的,当一个类被JVM加载的时候,该类的加载是线程安全的,相当于JVM对该过程加锁了。所以整个过程处于一个锁的范围内,然后静态成员变量进行初始化就相当于Student1()被new了,只会被new一次。

当第二个线程进来,它就发现这个类已经被加载了,就不需要进行加载了,对象也不需要频繁创建,所以线程是安全的!

总结

老刘看过很多关于java单例模式的资料,多多少少都会缺少一点细节,这次老刘把它补全了。

最后,如果觉得有哪里写的不好或者有错误的地方,可以联系公众号:努力的老刘,进行交流。

如果觉得写的不错,给老刘点个赞!

以上就是java 单例模式容易忽略的细节的详细内容,更多关于java 单例模式的资料请关注小牛知识库其它相关文章!

 类似资料:
  • 本文向大家介绍容易被忽略的Python内置类型,包括了容易被忽略的Python内置类型的使用技巧和注意事项,需要的朋友参考一下 Python中的内置类型是我们开发中最常见的,很多人都能熟练的使用它们。 然而有一些内置类型确实不那么常见的,或者说往往会被我们忽略,所以这次的主题就是带领大家重新认识这些“不同寻常”的内置类型。 (注意:本文基于python3,不会包含任何python2相关内容) fr

  • 本文向大家介绍PHP单例模式详细介绍,包括了PHP单例模式详细介绍的使用技巧和注意事项,需要的朋友参考一下 单例模式的概念 单例模式是指整个应用中某个类只有一个对象实例的设计模式。具体来说,作为对象的创建方式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统全局的提供这个实例。它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用。 单例模式的特点 单例模式的主要特点是“三私一公

  • 本文向大家介绍Python下简易的单例模式详解,包括了Python下简易的单例模式详解的使用技巧和注意事项,需要的朋友参考一下 Python 下的单例模式 要点: 1.某个类只能有一个实例; 2.它必须自行创建这个实例; 3.它必须自行向整个系统提供这个实例 方法:重写new函数 应该考虑的情况: 1.这个单例的类可能继承了别的类 2.这个单例的类还有可能要接收参数来实例化 要点: 实例化的过程其

  • 问题内容: 有人能想到一种忽略Java双击附带的单击的好方法吗? 我希望每种行为都有不同的行为,例如: 单击可在点击点上绘制十字准线 双击选择屏幕上的对象,但应该 不是 搽点击点十字线 …有人能想到一种方法吗?某种计时器设置可能是?一个想法赞赏:-) <免责声明> …而且是的,我知道我正在犯一个最令人发指的可用性/ UI伪装。 问题答案: 实际上,您需要在MouseAdapter的重写mouseC

  • 问题内容: 我一直在阅读有关OCP主体以及如何使用策略模式来实现此目的的信息。 我打算尝试向几个人解释这一点,但是我能想到的唯一示例是根据“订单”的状态使用不同的验证类。 我已经在线阅读了几篇文章,但这些文章通常不会描述使用该策略的真实原因,例如生成报告/账单/验证等。 您是否认为现实世界中有常见的战略模式示例? 问题答案: 你必须加密文件。 对于小文件,可以使用“内存中”策略,其中读取完整的文件

  • 本文向大家介绍java 单例模式的实例详解,包括了java 单例模式的实例详解的使用技巧和注意事项,需要的朋友参考一下 java 单例模式的实例详解 概念:    java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。    单例模式有一下特点:   1、单例类只能有一个实例。   2、单例类必须自己自己创建自己的唯一实例。   3、单例类必须给所有其他对