单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。比如,线程池、缓存等。
如果不用单例模式,那有什么办法能创建一个独一无二的对象吗?
可以使用 Java 的静态变量。
使用静态变量有什么缺点吗?
如果要将对象赋值给一个全局变量,那么在程序一开始就要创建好这个对象。如果创建这个对象需要消耗比较大的资源,同时本次程序的执行过程中又没有用到它,这就会很浪费。
使用单例模式就可以做到,在需要时才创建对象(lazy instantiaze)。
下面是经典的单例模式代码:
public class Singleton_1 { |
经典单例模式实现有什么问题吗?
在多线程环境下,如果两个线程一前一后都判断 if (uniqueInstance == null)
成立,则两个线程就会创建两个不同对象。这就违反了单例模式的初衷。
有什么办法解决吗?
在 getInstance()
方法上加 synchronized
关键字,保证同一时刻,只有一个线程可以去执行 new Singleton_1()
方法,其他线程在外面等待。
这样给 getInstance()
方法上加 synchronized
关键字会不会降低程序性能呢?还有其他什么好办法吗?(当然,如果 getInstance()
的性能对应用程序不是很关键,则完全不用进行优化)
优化方法 1:使用「急切(eagerly)」创建实例
public class Singleton_2 { |
优化方法 2:使用「双重检查加锁」,在 getInstance()
减少使用同步
public class Singleton_3 { |
这样可以保证线程安全了,但是 Java 中还有没有什么能力可以创建对象呢?
除了通过 new
一个对象外,还可以使用克隆、反射和反序列化等技术创建一个对象。这就对上述的单例模式造成了破坏。
克隆:使用浅拷贝的方式创建一个对象
反射:反射可以绕过构造函数的访问控制(通过
setAccessible(true)
允许访问私有构造函数),从而直接创建类的实例反序列化:在反序列化时,Java 会通过
readObject()
方法重新创建对象
那怎么解决呢?使用枚举类。
public enum Singleton_4 { |
枚举类是在 JVM 的加载过程中自动处理的,枚举类的实例在类加载的过程中就已经被创建。并且 JVM 会保证枚举实例的唯一性。枚举类的构造方法是私有的。
克隆:枚举类无法克隆
反射:通过反射调用枚举类的构造方法会抛异常:java.lang.reflect.InvocationTargetException
反序列化:对于枚举类,JVM 会自动处理反序列化,并确保反序列化时返回的是枚举类的唯一实例