在早期的项目代码中,如果我们想使用类的某个方法,我们基本都会创建一个类的对象实例然后再调用方法,这样的实现往往在系统内就会存在某个类的大量实例。如此一来,项目框架很难管理大量的对象,而且如果java虚拟机不能及时回收,容易造成内存溢出。

  首先我们要明白什么是单例,所谓单例就是说在项目框架内某个类的对象实例只存在一个,任何调用方获取到的对象实例都是一个,那么很明显这个类是不能够被外部直接调用类构造器创建的。

  我们先看下一个简单的单例设计:

  面试中的单例问题

上面代码在单线程是没有问题的,而且只有当线程调用类的静态方法时,才会生成类的静态变量。

 但是当多线程访问时,上面代码是有问题的,会生成多个对象的实例。

那么,我们可以用另外一种方法实现,比如说在类加载时候就初始化对象的实例,这样后面无论怎么调用类静态方法都不创建新的实例(饿汉模式)。

还有一种方法,但是会牺牲部分系统性能,意思就是在多线程访问方法时通过锁机制让线程排队访问。

我们先通过在类方法上加锁来实现类的单例,比如:

面试中的单例问题

上述方法能实现单例,而且采用的思路是延迟加载,但是执行效率比较低。

使用双重校验锁(Double Check Lock)机制来实现单例模式,一方面需要在实例上加上volatile关键字通知操作系统实现线程访问时内存屏障,避免指令重排序,然后还需要在方法中通过虚拟机实现的synchronized来同步方法访问,写法如下:

面试中的单例问题

上面的实现是比较复杂的,大家需要去了解的知识点比较多,比如volatile ,synchronized,内存屏障,指令重排序。

该方式可以实现单例模式,而且也是延迟加载。

如果说我们不考虑服务负载问题,在多线程环境下可以预先加载类的静态实例,当虚拟机加载完成类后就会创建类的静态变量,甭管你到时用不用,反正给你留在那里。

所有线程访问到的都是同一静态实例,有人也称这种方式为饿汉式,具体写法如下:

面试中的单例问题

上面写法实现单例也是没有问题的,但是有些同学就会觉得如果我只是想调用一个类的某个静态方法,并不想生成它的实例,那有没有其他方法呢,可以使用静态内部类来实现这个需求。

虚拟机在加载类的过程中一开始并不会初始化类的内部静态类。

如果线程调用内部静态类时,虚拟机只会初始化一次,这样既可以实现单例,同时也是线程安全的。

具体写法如下:

面试中的单例问题

除了以上讲到的几种方式外,JDK自身的枚举类型本身就是单例的实现,调用者不能显式的调用构造器完成实例创建,因此很多Java规范文档推荐使用枚举来实现单例。

现在的主流开发框架都提供单例/多例模式供开发者选择,这样的好处让开发者更多关注业务功能开发,而不用过多关注虚拟机内部类实例创建问题。

例如:spring中默认类注入就是单例的,可以根据实际情况设置scope为singleton(单例)或者prototype(多例),如下图所示:

面试中的单例问题

相关文章:

  • 2022-12-23
  • 2021-08-06
  • 2022-01-03
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-02-04
猜你喜欢
  • 2021-10-15
  • 2022-12-23
  • 2021-06-16
  • 2021-10-24
  • 2021-11-01
  • 2021-05-14
相关资源
相似解决方案