单例模式讨论的是一个应用中,一个类有且仅有一个实例的情况。 在Java中要讨论的是在每个JVM(java虚拟机)中,仅有一个实例。
注意两个词:每个JVM,一个实例。
接下来,让我们讨论一下在Java中如何实现单例模式。
代码量比较多。code example
不管有没有其他类调用这个实例,单例在系统类加载时就会被创建。
publicclassEagerSingleton {
privatestaticvolatileEagerSingletoninstance = newEagerSingleton();
publicstaticEagerSingletongetInstance() {
returninstance;
}
privateEagerSingleton() {
System.out.printf("%s Instance Created!\n", this.getClass().getSimpleName());
}
}这种方法仅有一个缺点,对象大时,占用系统资源较多。 如果对象不大,占用系统资源较少,这是一个实现单例的好方法。
在编程中,延迟初始化是一种编程技巧。在第一次需要时,才创建对象、计算值或者执行其他耗时的过程。
publicfinalclassLazySingleton {
privatestaticvolatileLazySingletoninstance = null;
privateLazySingleton() {}
publicstaticLazySingletongetInstance() {
if(instance == null){
synchronized (LazySingleton.class){
instance = newLazySingleton();
}
}
returninstance;
}
}Double Check:
publicstaticLazySingletonV2getInstance() {
if (instance == null) {
synchronized (LazySingletonV2.class) {
if (instance == null) {
instance = newLazySingletonV2();
// multi thread will generate two or more instances.System.out.println("instance address: " + instance);
}
}
}
returninstance;
}这种方法在类加载的时候创建实例。有一个缺点:就是假设这个类里有5个静态变量,代码仅需要访问2-3个变量,这时创建 instance就没有什么用。
publicclassStaticBlockSingleton {
privatestaticfinalStaticBlockSingletoninstance;
static {
instance = newStaticBlockSingleton();
}
privateStaticBlockSingleton() {}
publicstaticStaticBlockSingletongetInstance() {
returninstance;
}
}LazyHolder类在需要时才创建。同时我们还可以访问其他静态变量。
publicclassBillPughSingleton {
publicstaticintNUM = 10;
privateBillPughSingleton() {
System.out.println("create singleton.");
}
privatestaticclassLazyHolder {
static {
System.out.println("create class LazyHolder...");
}
privatestaticfinalBillPughSingletoninstance = newBillPughSingleton();
}
publicstaticBillPughSingletongetInstance() {
returnLazyHolder.instance;
}
}反序列化时会重新生成实例。在类中加入readResolve方法。返回当前实例。
This method will be invoked when you will de-serialize the object. Inside of this method, you must return the existing instance to ensure a single instance application wide.
publicclassDemoSingletonimplementsSerializable {
privatestaticvolatileDemoSingletoninstance = null;
publicstaticDemoSingletongetInstance() {
if (instance == null) {
instance = newDemoSingleton();
}
returninstance;
}
privateinti = 10;
// return existing instance.// protected Object readResolve(){// return instance;// }publicintgetI() {
returni;
}
publicvoidsetI(inti) {
this.i = i;
}
}序列化要加上序列号。若不加,这个了类如果修改的话,再次反序列化此前的类,会报 local class incompatible
Exceptioninthread"main"java.io.InvalidClassException: u021.seri.DemoSingleton; localclassincompatible: streamclassdescserialVersionUID = -6928200329713978600, localclassserialVersionUID = 2784835485903072265atjava.base/java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:689)
atjava.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1903)
atjava.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1772)
atjava.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2060)
atjava.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1594)
atjava.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:430)
atu021.seri.Client.main(Client.java:21)This problem can be solved only by adding a unique serial version id to the class. It will prevent the compiler from throwing the exception by telling it that both classes are same, and will load the available instance variables only.
经过上面的讨论,那么一个推荐的单例模式该如何写呢?
publicclassDemoSingletonimplementsSerializable {
privatestaticfinallongserialVersionUID = 1L;
privateDemoSingleton() {}
privatestaticclassDemoSingletonHolder {
privatestaticfinalDemoSingletoninstance = newDemoSingleton();
}
publicstaticDemoSingletongetInstance() {
returnDemoSingletonHolder.instance;
}
protectedObjectreadResolve() {
returngetInstance();
}
}Singleton is “one instance per JVM”, so each node will have its own copy of singleton.
类初始化在JVM层面是线程安全的。
publicclassBillPughSingleton {
privateBillPughSingleton() {
}
privatestaticclassLazyHolder {
privatestaticfinalBillPughSingletonINSTANCE = newBillPughSingleton();
}
publicstaticBillPughSingletongetInstance() {
returnLazyHolder.INSTANCE;
}
}As per you recommend above code template to design singleton class. But how Thread safety achieve in above code ??
It’s already threadsafe because java static field/class initialization is thread safe – at JVM level. Static initialization is performed once per class-loader and JVM ensures the single copy of static fields. So even if two threads access above code, only one instance of class will be created by JVM.