單例模式
1.懶漢模式:延遲加載,只有在真正使用的時候,才開始初始化,
1)執行緒安全問題
2)double check 加鎖優化
3)編譯器(JIT)、CPU有可能對指令進行重排序,導致使用到尚未初始化的實體,可以通過添加volatile關鍵字進行修飾
對于volatile修飾的欄位,可以防止指令重排,
public class LazySingletonTest { public static void main(String[] args) { LazySingleton instance = LazySingleton.getInstance(); LazySingleton instance1 = LazySingleton.getInstance(); System.out.println(instance == instance1); } } class LazySingleton{ private static LazySingleton instance; private LazySingleton(){} public static LazySingleton getInstance() { if(instance == null){ instance = new LazySingleton(); } return instance; } }
在多執行緒環境下會出現問題:如下面代碼:
public class LazySingletonTest { public static void main(String[] args) { new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); } } class LazySingleton{ private static LazySingleton instance; private LazySingleton(){} public static LazySingleton getInstance() { if(instance == null){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } instance = new LazySingleton(); } return instance; } }
列印結果:
com.kzp.designpattern.lazysingleton.LazySingleton@206683de
com.kzp.designpattern.lazysingleton.LazySingleton@37d3a78
在多執行緒環境下,列印了兩個不同的物件,getInstance方法前加入synchronized關鍵字可解決該多執行緒問題,
public class LazySingletonTest { public static void main(String[] args) { /* LazySingleton instance = LazySingleton.getInstance(); LazySingleton instance1 = LazySingleton.getInstance(); System.out.println(instance == instance1);*/ new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); } } class LazySingleton{ private static LazySingleton instance; private LazySingleton(){} public synchronized static LazySingleton getInstance() { if(instance == null){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } instance = new LazySingleton(); } return instance; } }
但加入synchronized會有性能問題,下面是用雙重檢查機制來創建單例物件,
public class LazySingletonTest { public static void main(String[] args) { /* LazySingleton instance = LazySingleton.getInstance(); LazySingleton instance1 = LazySingleton.getInstance(); System.out.println(instance == instance1);*/ new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); new Thread(()->{ LazySingleton instance = LazySingleton.getInstance(); System.out.println(instance); }).start(); } } class LazySingleton{ private volatile static LazySingleton instance; private LazySingleton(){} public static LazySingleton getInstance() { if(instance == null){ synchronized (LazySingleton.class){ if(instance == null) { instance = new LazySingleton(); } } } return instance; } }
初始化物件的步驟為:
//1.分配空間
//2.初始化
//3.參考賦值
加入volatile可以防止指令的重排序
2.餓漢模式:
類加載的初始化階段就完成了實體的初始化,本質上就是借助于JVM類加載機制,保證實體的唯一性,
類加載程序:
1.加載二進制資料到記憶體中,生成對應的Class資料結構,
2.連接:a:驗證 b:準備(給類的靜態成員變數賦默認值)c:決議
3.初始化:給類的靜態變數賦初值,
只有在真正使用對應的類時,才會觸發初始化 如(當前類是啟動類即main函式所在類,直接進行new 操作,訪問靜態屬性,訪問靜態方法,
用反射訪問類,初始化一個類的子類等)
public class HungrySingletonTest { public static void main(String[] args) { HungrySingleton instance = HungrySingleton.getInstance(); HungrySingleton instance1 = HungrySingleton.getInstance(); System.out.println(instance == instance1); } } class HungrySingleton{ private static HungrySingleton instance = new HungrySingleton(); private HungrySingleton(){ } public static HungrySingleton getInstance() { return instance; } }
3.靜態內部類
1)本質上是利用類的加載機制來保證執行緒安全,
2)只有在實際使用的時候,才會觸發類的初始化,所以也是懶加載的一種形式,
public class InnerClassSingletonTest { public static void main(String[] args) { InnerClassSingleton instance = InnerClassSingleton.getInstance(); InnerClassSingleton instance1 = InnerClassSingleton.getInstance(); System.out.println(instance == instance1); } } class InnerClassSingleton{ private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
多執行緒下代碼:
public class InnerClassSingletonTest { public static void main(String[] args) { new Thread(()->{ InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(instance); }).start(); new Thread(()->{ InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(instance); }).start(); } } class InnerClassSingleton{ private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
列印結果為
com.kzp.designpattern.innerclasssingleton.InnerClassSingleton@206683de
com.kzp.designpattern.innerclasssingleton.InnerClassSingleton@206683de
因此這種方式是執行緒安全的,
但是,可以通過發射來創建多實體,違反單例模式:
public class InnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance(); InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(innerClassSingleton == instance); } } class InnerClassSingleton{ private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
但靜態內部類和餓漢模式可以防止創建多實體,懶漢模式則不可以
通過在私有建構式中判斷物件是否存在,存在則拋出例外來避免反射創建多實體,
public class InnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor(); declaredConstructor.setAccessible(true); InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance(); InnerClassSingleton instance = InnerClassSingleton.getInstance(); System.out.println(innerClassSingleton == instance); } } class InnerClassSingleton{ private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ if(InnerClassHolder.instance != null){ throw new RuntimeException("單例不允許多個實體"); } } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
4.列舉單實體方式
public enum EnumSingleton { INSTANCE; public void print(){ System.out.println(this.hashCode()); } } class EnumTest{ public static void main(String[] args) { EnumSingleton instance = EnumSingleton.INSTANCE; EnumSingleton instance1 = EnumSingleton.INSTANCE; System.out.println(instance == instance1); } }
下面通過反射來創建列舉單實體物件
public enum EnumSingleton { INSTANCE; public void print(){ System.out.println(this.hashCode()); } } class EnumTest{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { /* EnumSingleton instance = EnumSingleton.INSTANCE; EnumSingleton instance1 = EnumSingleton.INSTANCE; System.out.println(instance == instance1);*/ Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class); declaredConstructor.setAccessible(true); declaredConstructor.newInstance("INSTANCE",0); } }
拋出例外
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:402)
at com.kzp.designpattern.enumsingleton.EnumTest.main(EnumSingleton.java:19)
序列化與反序列化實體物件后,單例物件遭到了破壞,
public class InnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { //測驗序列化 InnerClassSingleton instance = InnerClassSingleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable")); oos.writeObject(instance); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable")); InnerClassSingleton object = (InnerClassSingleton) ois.readObject(); System.out.println(instance == object); } } class InnerClassSingleton implements Serializable { private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ if(InnerClassHolder.instance != null){ throw new RuntimeException("單例不允許多個實體"); } } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } }
結果回傳false
在Serializable介面中有這樣的描述:
Classes that need to designate a replacement when an instance of it * is read from the stream should implement this special method with the * exact signature. * * <PRE> * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; * </PRE><p>
自定義實作readResolve可解決這個問題
public class InnerClassSingletonTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { //測驗序列化 InnerClassSingleton instance = InnerClassSingleton.getInstance(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable")); oos.writeObject(instance); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable")); InnerClassSingleton object = (InnerClassSingleton) ois.readObject(); System.out.println(instance == object); } } class InnerClassSingleton implements Serializable { private static class InnerClassHolder{ private static InnerClassSingleton instance = new InnerClassSingleton(); } private InnerClassSingleton(){ if(InnerClassHolder.instance != null){ throw new RuntimeException("單例不允許多個實體"); } } public static InnerClassSingleton getInstance(){ return InnerClassHolder.instance; } Object readResolve() throws ObjectStreamException{ return InnerClassHolder.instance; }; }
但是加入這個之后,會報
Exception in thread "main" java.io.InvalidClassException: com.kzp.designpattern.innerclasssingleton.InnerClassSingleton; local class incompatible: stream classdesc serialVersionUID = 595850868840365671, local class serialVersionUID = 7367063990306020841 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:621) at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1623) at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1774) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371) at com.kzp.designpattern.innerclasssingleton.InnerClassSingletonTest.main(InnerClassSingletonTest.java:32)
這是因為我們沒有給需要序列化的類給一個serialVersionUID,導致序列化和反序列化時認為為不同的類,
InnerClassSingleton類中加入serialVersionUID即可解決該問題,
列舉型別的單實體則不存在這樣的反序列化問題,
public enum EnumSingleton { INSTANCE; public void print(){ System.out.println(this.hashCode()); } } class EnumTest{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException { EnumSingleton instance = EnumSingleton.INSTANCE; // ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("enumsingleton")); // oos.writeObject(instance); // oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("enumsingleton")); EnumSingleton object = ((EnumSingleton) ois.readObject()); System.out.println(instance == object); } }
結果為true,
原始碼中的應用
//Spring & JDK java.lang.Runtime org.springframework.aop.framework.ProxyFactoryBean org.springframework.beans.factory.support.DefaultSingletonBeanRegistry org.springframework.core.ReactiveAdapterRegistry //Tomcat org.apache.catalina.webresources.TomcatURLStreamHandlerFactory //反序列化指定資料源 java.util.Currency
單例模式在JDK中的應用:
Runtime類
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {}
使用到的是餓漢模式,
Currency類中用到了解決反序列化問題的方法,
public final class Currency implements Serializable private final String currencyCode; private Object readResolve() { return getInstance(currencyCode); } }
Spring中DefaultSingletonBeanRegistry類使用到了單例模式
另外,ReactiveAdapterRegistry(Srping5版本才有的類)使用到了雙重檢查模式:
public class ReactiveAdapterRegistry { @Nullable private static volatile ReactiveAdapterRegistry sharedInstance; public static ReactiveAdapterRegistry getSharedInstance() { ReactiveAdapterRegistry registry = sharedInstance; if (registry == null) { Class var1 = ReactiveAdapterRegistry.class; synchronized(ReactiveAdapterRegistry.class) { registry = sharedInstance; if (registry == null) { registry = new ReactiveAdapterRegistry(); sharedInstance = registry; } } } return registry; } }
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/29309.html
標籤:設計模式
