二、單例模式之單例模式
阿新 • • 發佈:2019-01-06
單例模式建立方式有以下幾種方式:
- 餓漢模式
- 懶漢模式
- 註冊式模式
- 列舉式模式
- 序列化模式
1.餓漢模式
在類載入時初始化,也是利用類載入執行緒安全的特性確保了單例例項化的執行緒安全。
package com.kancy.pattern.single; /** * 單例模式 - 餓漢模式 * @author kancy */ public class HungerSingleton { /** * 在類載入時初始化好單例物件,利用類載入來保證單例初始化的執行緒安全 * 優點:不用任何鎖就能保證絕對執行緒安全,執行效率高 * 缺點:由於類載入時就分配記憶體空間,初始化。如果以後使用不到改物件,就會造成一定程度記憶體空間的浪費。*/ private static HungerSingleton ourInstance = new HungerSingleton(); private HungerSingleton() { } public static HungerSingleton getInstance() { return ourInstance; } }
2.懶漢模式
1)方式一:
package com.kancy.pattern.single; /** * 單例模式 - 懶漢模式01 * @author kancy*/ public class LazySingleton01 { /** * 單例被使用時才建立例項物件,不會造成記憶體空間浪費,但會存著資料安全問題: * 兩個執行緒同一時刻判斷instance == null 時,都會去建立物件,後者會覆蓋前者引用。 */ private static LazySingleton01 instance; private LazySingleton01() { } public static LazySingleton01 getInstance(){ if(instance == null){ instance = new LazySingleton01(); } return instance; } }
2)方式二:
package com.kancy.pattern.single; /** * 單例模式 - 懶漢模式02 * @author kancy */ public class LazySingleton02 { /** * 單例被使用時才建立例項物件,不會造成記憶體空間浪費,但會存著資料安全問題: * 兩個執行緒同一時刻判斷instance == null 時,都會去建立物件,後者會覆蓋前者引用。 * 加上方法可見鎖,確保執行緒安全,但同時會較低執行效率,效能下降。 */ private static LazySingleton02 instance; private LazySingleton02() { } public synchronized static LazySingleton02 getInstance(){ if(instance == null){ instance = new LazySingleton02(); } return instance; } }
3)方式三:
package com.kancy.pattern.single; /** * 單例模式 - 懶漢模式03 * @author kancy */ public class LazySingleton03 { /** * 單例被使用時才建立例項物件,不會造成記憶體空間浪費,但會存著資料安全問題: * 兩個執行緒同一時刻判斷instance == null 時,都會去建立物件,後者會覆蓋前者引用。 * 使用物件鎖,縮小鎖的範圍,確保執行緒安全的同時,減小效能的損失 */ private static volatile LazySingleton03 instance; // 保證物件在記憶體中的可見性 private LazySingleton03() { } public static LazySingleton03 getInstance(){ if(instance == null){ synchronized (LazySingleton03.class){ // double check,防止物件沒有初始化完整時,第二個執行緒再次進來進行初始化 if(instance == null){ instance = new LazySingleton03(); } } } return instance; } }
4)方式四:
package com.kancy.pattern.single; import java.util.concurrent.atomic.AtomicBoolean; /** * 單例模式 - 懶漢模式04 * @author kancy */ public class LazySingleton04 { /** * 可以通過反射修改initFlg再次進行反射初始化物件,目前沒有好的辦法解決 * 但一般沒人這麼無聊吧!!! */ private static AtomicBoolean initFlg = new AtomicBoolean(false); private LazySingleton04() { // 防止反射入侵:不允許再次建立例項 if(!initFlg.get()){ initFlg.set(true); }else{ throw new RuntimeException("單例被反射入侵"); } } /** * 利用內部類載入的機制巧妙的實現單例的執行緒安全。 * 內部類在使用時才會進行類載入。 * 當呼叫getInstance方法時,才會把內部類LazySingleton04Holder載入到記憶體,同時執行緒安全的初始化LazySingleton04例項。 * 這樣既可以保證執行緒安全,也可以減少記憶體浪費的機會。 */ public static LazySingleton04 getInstance(){ return LazySingleton04Holder.instance; } private static class LazySingleton04Holder{ private static final LazySingleton04 instance = new LazySingleton04(); } }
效率從高到低:LazySingleton04 - > LazySingleton01 -> LazySingleton03 -> LazySingleton02,所以推薦使用第四種。
3.註冊式
把單例註冊到容器中,有則取出,無則建立並註冊
package com.kancy.pattern.single; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 單例模式 - 註冊式單例模式 */ public class RegisterSingleton { /** * 讀取實現執行緒安全 */ private static Map<String,Object> ioc = new ConcurrentHashMap(); public static Object getBean(String className){ if(!ioc.containsKey(className)){ try { // 建立物件,並且註冊到容器當中 Class<?> aClass = Class.forName(className); Object instance = aClass.newInstance(); // 建立物件到put進入ioc中需要一段時間,可能造成執行緒不安全 ioc.put(className, instance); } catch (Exception e) { e.printStackTrace(); } } return ioc.get(className); } }
4.列舉式
package com.kancy.pattern.single; /** * 單例模式 - 常量/列舉式單例模式 */ public enum EnumSingleton { A,B,C; }
5.序列化方式
序列化:將記憶體狀態轉化為位元組陣列,持久化到磁碟等。
反序列化:將持久化內容轉化為java物件。
附上測試類程式碼:
package com.kancy.pattern.single; import com.kancy.bean.Person; import java.io.Serializable; import java.util.concurrent.CountDownLatch; public class SingletonTest implements Serializable,Cloneable{ public static void main(String[] args) { testDataSecure(); } /** * 測試資料安全 */ private static void testDataSecure() { int threadCount = 20; CountDownLatch count = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { new Thread(() -> { try { // 等待所有執行緒都建立好,一起去執行 count.await(); Object bean = RegisterSingleton.getBean(Person.class.getName()); System.out.println(bean); } catch (Exception e) { e.printStackTrace(); } }).start(); // 執行緒就緒 count.countDown(); } } /** * 懶漢效率從高到底: * LazySingleton04 - > LazySingleton01 -> LazySingleton03 -> LazySingleton02 */ private static void testEfficiency() { long start = System.currentTimeMillis(); for (int i = 0; i < 100000000; i++) { LazySingleton04.getInstance(); } long end = System.currentTimeMillis(); System.out.println("用時:" + (end - start)); } }