單例模式
Java內(nèi)存模型的抽象示意圖:
所有單例模式都有一個(gè)共性,那就是這個(gè)類沒有自己的狀態(tài)。也就是說無(wú)論這個(gè)類有多少個(gè)實(shí)例,都是一樣的;然后除此者外更重要的是,這個(gè)類如果有兩個(gè)或兩個(gè)以上的實(shí)例的話程序會(huì)產(chǎn)生錯(cuò)誤。
非線程安全的模式
public class Singleton { private static Singleton instance; private Singleton(){ } public static Singleton getInstance() { if (instance == null) //1:A線程執(zhí)行 instance = new Singleton(); //2:B線程執(zhí)行 return instance; } }
普通加鎖
public class SafeLazyInitialization { private static Singleton instance; public synchronized static Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; } }
出于性能考慮,采用雙重檢查加鎖的模式
雙重檢查加鎖模式
public class Singleton{ private static Singleton singleton; private Singleton(){ } public static Singleton getInstance(){ if(null == singleton){ //第一次檢查 synchronized(Singleton.class){ //加鎖 if(null == singleton){ //第二次檢查 singleton = new Singleton();//問題的根源出在這里 } } } return singleton; } }
雙重檢查加鎖模式
相對(duì)于普通的單例和加鎖模式而言,從性能和線程安全上來(lái)說都有很大的提升和保障。然而雙重檢查加鎖模式
也存在一些隱蔽不易被發(fā)現(xiàn)的問題。首先我們要明白在JVM創(chuàng)建新的對(duì)象時(shí),主要要經(jīng)過三個(gè)步驟。
分配內(nèi)存
初始化構(gòu)造器
將對(duì)象指向分配的內(nèi)存地址
這樣的順序在雙重加鎖模式下是么有問題的,對(duì)象在初始化完成之后再把內(nèi)存地址指向?qū)ο蟆?/p>
問題的根源
但是現(xiàn)代的JVM為了追求執(zhí)行效率會(huì)針對(duì)字節(jié)碼(編譯器級(jí)別
)以及指令和內(nèi)存系統(tǒng)重排序(處理器重排序
)進(jìn)行調(diào)優(yōu),這樣的話就有可能
(注意是有可能
)導(dǎo)致2和3的順序是相反的,一旦出現(xiàn)這樣的情況問題就來(lái)了。
java源代碼到最終實(shí)際執(zhí)行的指令序列:
前面的雙重檢查鎖定示例代碼的(instance = new Singleton();)創(chuàng)建一個(gè)對(duì)象。這一行代碼可以分解為如下的三行偽代碼:
memory = allocate(); //1:分配對(duì)象的內(nèi)存空間ctorInstance(memory); //2:初始化對(duì)象instance = memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址
上面三行偽代碼中的2和3之間,可能會(huì)被重排序(在一些JIT編譯器上,這種重排序是真實(shí)發(fā)生的,詳情見參考文獻(xiàn)1的“Out-of-order writes”部分)。2和3之間重排序之后的執(zhí)行時(shí)序如下:
memory = allocate(); //1:分配對(duì)象的內(nèi)存空間instance = memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址 //注意,此時(shí)對(duì)象還沒有被初始化!ctorInstance(memory); //2:初始化對(duì)象
多線程并發(fā)執(zhí)行的時(shí)候的情況:
解決方案
基于Volatile的解決方案
先來(lái)說說Volatile
這個(gè)關(guān)鍵字的含義:
可以很好地解決可見性問題
但不能確保原子性問題(通過
synchronized
進(jìn)行解決)禁止指令的重排序(單例主要用到此JVM規(guī)范)
Volatile 雙重檢查加鎖模式
public class Singleton{ private volatile static Singleton singleton; private Singleton(){ } public static Singleton getInstance(){ if(null == singleton){ synchronized(Singleton.class){ if(null == singleton){ singleton = new Singleton(); } } } return singleton; } }
基于類初始化的解決方案
利用靜態(tài)內(nèi)部類的方式來(lái)創(chuàng)建,因?yàn)殪o態(tài)屬性由JVM確保第一次初始化時(shí)創(chuàng)建,因此也不用擔(dān)心并發(fā)的問題出現(xiàn)。當(dāng)初始化進(jìn)行到一半的時(shí)候,別的線程是無(wú)法使用的,因?yàn)镴VM會(huì)幫我們強(qiáng)行同步這個(gè)過程。另外由于靜態(tài)變量只初始化一次,所以singleton仍然是單例的。
這個(gè)方案的實(shí)質(zhì)是:允許“問題的根源”的三行偽代碼中的2和3重排序,但不允許非構(gòu)造線程(這里指線程B)“看到”這個(gè)重排序。
靜態(tài)內(nèi)部類
的方式
public class Singleton{ private Singleton(){} public static Singleton getInstance(){ return InnerClassSingleton.singleton; } private class InnerClassSingleton{ protected static Singleton singleton = new Singleton(); } }
然而,雖然靜態(tài)內(nèi)部類
模式可以很好地避免并發(fā)創(chuàng)建出多個(gè)實(shí)例的問題,但這種方式仍然有其存在的隱患。
存在的隱患
一旦一個(gè)實(shí)例被持久化后重新生成的實(shí)例仍然有可能是不唯一的。
由于java提供了反射機(jī)制,通過反射機(jī)制仍然有可能生成多個(gè)實(shí)例。
序列化和反序列化帶來(lái)的問題
:反序列化后兩個(gè)實(shí)例不一致了。
private static void singleSerializable() { try (FileOutputStream fileOutputStream=new FileOutputStream(new File("myObjectFilee.txt")); ObjectOutputStream objectOutputStream=new ObjectOutputStream(fileOutputStream);) {// SingletonObject singletonObject = SingletonObject.getInstance();// InnerClassSingleton singletonObject = InnerClassSingleton.getInstance(); EnumSingleton singletonObject = EnumSingleton.INSTANCE; objectOutputStream.writeObject(singletonObject); objectOutputStream.close(); fileOutputStream.close(); System.out.println(singletonObject.hashCode()); } catch (IOException e) { e.printStackTrace(); } try (FileInputStream fileInputStream=new FileInputStream(new File("myObjectFilee.txt")); ObjectInputStream objectInputStream=new ObjectInputStream(fileInputStream);) {// SingletonObject singleTest=(SingletonObject) objectInputStream.readObject();// InnerClassSingleton singleTest=(InnerClassSingleton) objectInputStream.readObject(); EnumSingleton singleTest=(EnumSingleton) objectInputStream.readObject(); objectInputStream.close(); fileInputStream.close(); System.out.println(singleTest.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
問題點(diǎn)及解決辦法
ObjectInputStream中的readOrdinaryObject
。
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) { Object rep = desc.invokeReadResolve(obj); if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { handles.setObject(passHandle, obj = rep); } }
調(diào)用自定義的readResolve
方法
protected Object readResolve(){ System.out.println("調(diào)用了readResolve方法!"); return InnerClassSingleton.getInstance(); }
通過反射機(jī)制獲取到兩個(gè)不同的實(shí)例
private static void attack() { try { Class<?> classType = InnerClassSingleton.class; Constructor<?> constructor = classType.getDeclaredConstructor(null); constructor.setAccessible(true); InnerClassSingleton singleton = (InnerClassSingleton) constructor.newInstance(); InnerClassSingleton singleton2 = InnerClassSingleton.getInstance(); System.out.println(singleton == singleton2); //false } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
解決方案
: 私有構(gòu)造方法中進(jìn)行添加標(biāo)志判斷。
private InnerClassSingleton() { synchronized (InnerClassSingleton.class) { if (false == flag) { flag = !flag; } else { throw new RuntimeException("單例模式正在被攻擊"); } } }
單例最優(yōu)方案,枚舉
的方式
枚舉實(shí)現(xiàn)單例的優(yōu)勢(shì)
自由序列化;
保證只有一個(gè)實(shí)例(即使使用反射機(jī)制也無(wú)法多次實(shí)例化一個(gè)枚舉量);
線程安全;
public enum Singleton { INSTANCE; private Singleton(){} }
Hibernate的解決方案
通過ThreadLocal
的方式
import org.hibernate.HibernateException;import org.hibernate.Session;import org.hibernate.cfg.Configuration;public class HibernateSessionFactory { private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml"; private static final ThreadLocal threadLocal = new ThreadLocal(); private static Configuration configuration = new Configuration(); private static org.hibernate.SessionFactory sessionFactory; private static String configFile = CONFIG_FILE_LOCATION; static { try { configuration.configure(configFile); sessionFactory = configuration.buildSessionFactory(); } catch (Exception e) { System.err.println("%%%% Error Creating SessionFactory %%%%"); e.printStackTrace(); } } private HibernateSessionFactory() { } public static Session getSession() throws HibernateException { Session session = (Session) threadLocal.get(); if (session == null || !session.isOpen()) { if (sessionFactory == null) { rebuildSessionFactory(); } session = (sessionFactory != null) ? essionFactory.openSession() : null; threadLocal.set(session); } return session; }// Other methods...}
參考文檔:
掃描公眾號(hào),關(guān)注更多信息
---------------------------------------------------------------------------------我是分割線--------------------------------------------------------------------------
to be a better me, talk is cheap show me the code
版權(quán)所有,轉(zhuǎn)載請(qǐng)注明原文鏈接。
文中有不妥或者錯(cuò)誤的地方還望指出,以免誤人子弟。如果覺得本文對(duì)你有所幫助不妨【推薦】一下!如果你有更好的建議,可以給我留言討論,共同進(jìn)步!
再次感謝您耐心的讀完本篇文章。
http://www.cnblogs.com/rwxwsblog/p/6662951.html