
本文共 6271 字,大约阅读时间需要 20 分钟。
一、类加载的方式是按需加载,且只加载一次
在Java中,类是按需加载的,这种特性使得单例类的实现更加高效。对于单例类而言,只要在程序运行过程中被首次使用,就会被加载,并且在整个应用生命周期内不会再次加载。这种特性使得单例类在多线程环境下的使用更加安全,因为在线程访问单例对象之前,类已经被加载好了,并且只会返回一个实例。
在单例类被加载时,由于类在整个应用生命周期中只会被加载一次,因此只会创建一个实例。这意味着无论多少次调用单例类的获取方法,系统都会返回同一个实例。这种机制天生地保证了线程的安全性,因为当第一个线程获取单例对象时,其他线程在等待其完成后,就不会需要再次进行类的加载和构造实例的操作,从而避免了多线程竞态情况下的并发问题。
二、单例模式的几种实现
单例模式是一种常用的设计模式,其特点是确保一个类在其应用生命周期内只能有一个实例。这通常通过 vài种方式来实现,包括饿汉式和懒汉式。饿汉式在类加载时就创建实例,而懒汉式则在首次调用时创建实例。下面我们来看两种最常见的实现方式。
饿汉式单例
饿汉式单例的实现方式是通过设置一个静态变量在类加载时就创建实例。这种方式类似于JVM的类加载机制,确保在类第一次被使用时,实例已经被创建。这使得饿汉式单例在多线程环境下自然而然地支持线程安全,因为类加载过程和实例的创建都是在_assignedynamically_ in the class load order during the first access。这种实现方式的缺点是,如果单例类的创建耗时较长,可能会在类加载时优化性能带来一定的开销,但这种成本通常是值得的,特别是当单例类的创建可以在类加载阶段完成时。
代码示例如下:
懒汉式单例
懒汉式单例的实现方式则是在首次需要获取单例实例时才去创建它。这种方式的潜在优点是只有在单例被实际使用时才会进行昂贵的初始化操作,从而节省了资源。在换句话说,只有当某个应用模块需要用到单例实例时,才会去创建它。这种方式在单例实例的构造过程中不需要任何启动性开销,在多数情况下都表现得更好。然而,这种实现方式需要在实现时增加额外的锁机制,来保证多线程环境下的正确性,否则可能会导致多个实例被创建,从而破坏单例的原则。
代码示例如下:
加锁懒汉式双重锁模式
为了在懒汉式单例中确保线程安全,通常需要在实现时增加适当的锁机制。最简单的做法是使用synchronized关键字对真实单例的创建加锁,同时确保多次获取时不会进行重复处理。这种方式虽然能够满足线程安全的需求,但由于锁是全局的,可能会带来一定的性能开销。这时我们可以考虑使用一种更高效的方式来代替synchronized关键字,比如使用显式锁(lock),或者其他轻量级的锁机制。
代码示例如下:
双重锁模式
为了进一步优化锁的性能,可以采用双重锁的方式。这种方式通过将锁应用到具体的单例实例上,而不是整个类,这样可以减少加锁的开销。另外,为了确保在多线程环境下的可见性,需要将单例实例设为volatile变量,这样不同线程能够正确地看到同一个实例。这种方式能够在不同的线程间提供较高的吞吐量,同时又保证了线程的安全性。
代码示例如下:
静态内部类模式
对于一些特定的场景,单例模式实现可以更高效和简洁。例如,当我们需要确保在类的使用过程中,单例对象的创建只能由特定的逻辑完成时,可以采用静态内部类的方式。这种方式利用了JVM的类加载机制,在第一次access getInstance method时,内部类将被加载并生成实例。这会确保在多线程环境中,由于类加载是从一个线程完成的,因此在对单例对象进行访问时可以更安全地避免多线程竞态的问题。
代码示例如下:
三、单例模式与静态类的区别
有时候,在设计单例模式时,有人可能会质疑,为什么不将一个类中的所有属性和方法都定义成静态的呢?毕竟,在Java中,静态类不需要实例化,而单例类只允许一个实例的存在,这样双方都能够省去实例化的步骤。这种比较是不正确的,因为它们的目标不同。单例模式的目的是强制一个类只能有一个实例存在,这样可以避免资源浪费和当初的设计初衷。而静态类可能只是因为所有属性和方法都无需与特定的对象实例关联而被设计成静态的,这种情况下,类本身已经是最适合的使用方式,不需要实例化就能获取服务。
然而,单例模式在设计上依然具有面向对象的特性,允许类继承和方法重写,增加了类的灵活性。而将所有属性和方法设置为静态的方式则缺少了这些特性。因此,单例模式的实现方式通常会使用私有构造方法,确保只有类本身或其他受信任的逻辑能够创建实例,这是单例模式设计的核心思想之一。
四、Java反射攻击破坏单例模式
尽管现代Java的单例模式通常依赖于静态变量和scMARHADE代码的保护机制来保证实例的唯一性,但仍然存在潜在的安全问题。例如,攻击者可以通过Java反射机制获取类的构造函数,并绕过默认的访问权限,从而在没有合法权限的情况下创建多个单例实例。在实际开发中,这种问题需要被充分考虑到,并采取相应的防护措施。
示例代码:
解决方案
要防止反射攻击,可以在单例类的构造函数中添加一个判断,确保只有在单例类本身或其他受信任的类才能创建实例。这种方式即通过在构造函数中加入逻辑,确保实例的唯一性。具体实现方式是通过检查当前被创建的单例实例是否已经被初始化,若已初始化则抛出异常,防止反射绕过安全机制从而创建多个实例。
修改后的代码示例如下:
五、单例对象是否会被垃圾回收
在JVM中,对象的回收是基于可达性原则进行的。由于单例对象通常会被静态变量所引用,因此可以说它们是被其他对象所依赖的。在JDK1.2及以后的JVM版本中,由于类加载器和垃圾回收机制的优化,静态变量的引用会阻止对象的回收。在这种情况下,单例对象一般不会被垃圾回收。而对于JVM的方法区,单例类和其相关的静态变量也不会被回收,因为它们被JVM内部的类加载器结构所保留。因此在大多数情况下,单例实例会被JVM持有,并不会因为垃圾回收而被释放。
此外,对于现代JVM的运行时,像Oracle JDK HotSpot中,垃圾回收器会对大规模并发应用中的对象进行标记清除和复制收集算法。单例对象的引用关系非常紧密,因此它们常常不会被回收。在JVM的方法区中,类的信息存储在元空间中,这些信息不会被垃圾回收,除非整个类加载器被卸载,而这在正常应用中是不太可能发生的。
六、单例模式在多JVM/ClassLoader环境中的使用
在多JVM或者多ClassLoader环境下,单例模式需要特别注意这一点:不同的ClassLoader可能会加载同一个类的不同版本。这意味着,对于类Singleton.class,同一个ClassLoader可能会有不同的版本,在不同的JVM中,类的版本可能也会不同。这意味着在多JVM/ClassLoader环境中,单例对象反而是不同的,而不仅仅是同一个类的不同实例。因此,在这种情况下,单例模式的单例实例就不再是唯一的了。
要解决这个问题,最好的方法是确保单例类在全局范围内只能有一个实例,而且要保证不同ClassLoader之间不能共享同一个单例实例。这通常需要通过设置严格的反射保护机制来实现,或者在单例对象的配制中采用适当的方式来隔离不同ClassLoader的实例。但是,这样的实现往往会使单例对象变得更加复杂,并且可能会影响性能表现。因此,在实际应用中,需要根据具体需求来决定是否采用这样的优化措施。
七、Spring框架的单例实现
Spring框架之所以能够在单例模式的实现中发挥重要作用,是因为它提供了一个强大的依赖注入和控制反转(IOC)框架。在Spring中,通过配置可以指定哪种Bean的作用域。此外,Spring的IOC容器会自动管理Bean的生命周期,确保在整个应用中只创建一个实例。这种方式能够很好地替代传统的单例模式实现,并且还发挥了更加高效的作用。
在Spring中默认,所有Bean的作用域都是在某个范围内的。如果没有特别指定,Sprite的默认作用域通常是“singleton scope”,也就是单例作用域。在这种情况下,Spring的IOC容器会在第一次获取Bean实例时,创建一个实例,并将其缓存起来,后续的引用都将返回同一个实例。通过这种方式,Spring能够确保在应用程序中只存在一个实例,同时又能够通过简单的配置方式进行扩展和定制。
具体实现方式是通过ADDINGっ`),可以看到Spring在处理单例Bean的时候会调用AutowiredAnnotation(AutowiredQualifier))。当需要 @@scope("singleton") annotation来进行配置时,Spring会确保只有一个实例被创建,并将其存储在容器中供注入使用。此外,Spring的IOC容器内部通常使用一个map来存储Bean实例,这样可以快速地通过Bean的ID进行查找和注入,避免了频繁地重新创建实例带来的性能开销。
总结来说,Spring的单例实现通过将单例模式的实现细粒化,并利用其强大的反射机制,可以更加灵活地进行控制和管理。在实现细节上,Spring需要使用更高效的方式,比如利用JVM的动态代理机制来实现Bean的创建和注入,从而在性能和功能上都能够满足用户的需求。
推荐资料:
- [CDN文章1](https://blog.csdn.net/Ricky_Monarch/article/details/99407326) - [CDN文章2](https://blog.csdn.net/tjiyu/article/details/76572617) - [CDN文章3](https://blog.csdn.net/qq_36523667/article/details/79014324) - [CDN文章4](https://blog.csdn.net/naerna/article/details/80498633) - [CDN文章5](https://blog.csdn.net/zcw4237256/article/details/79670608)