Spring中常見的設計模式——單例模式


一、單例模式的應用場景

  單例模式(singleton Pattern)是指確保一個類在任何情況下都絕對只有一個實例,並提供一個全局訪問點。J2EE中的ServletContext,ServletContextConfig等;Spring中的ApplicationContext、數據庫連接池等。

二、餓漢式單例模式

  餓漢式單例模式在類加載的時候就立即初始化,並且創建單例對象。它是絕對的線程安全、在線程還沒出現以前就實現了,不可能存在訪問安全問題。

  優點:沒有增加任何鎖,執行效率高,用戶體驗比懶漢式好。

  缺點:類加載的時候就初始化了,用不用都進行,浪費內存。

  Spring 中IoC容器ApplocationContext本身就是典型的餓漢式單例模式:

public class HungrySingleton {
    private static final HungrySingleton h = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        return h;
    }
}

  餓漢式單例模式適用於單例對象較少的情況。

三、懶漢式單例模式

  被外部調用才會加載:

public class LazySimpleSingleton {
    private LazySimpleSingleton() {
    }

    private static LazySimpleSingleton lazy = null;

    public static LazySimpleSingleton getInstance() {
        if (lazy == null) {
            lazy = new LazySimpleSingleton();
        }
        return lazy;
    }
}

利用線程創建實例:

public class ExectorThread implements Runnable {
    @Override
    public void run() {
        LazySimpleSingleton simpleSingleton = LazySimpleSingleton.getInstance();
        System.out.println(Thread.currentThread().getName() + ":" + simpleSingleton);
    }
}

客戶端代碼:

public class LazySimpleSingletonTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new ExectorThread());
        Thread t2 = new Thread(new ExectorThread());
        t1.start();
        t2.start();
        System.out.println("END");
    }
}

結果:

END
Thread-1:singleton.Lazy.LazySimpleSingleton@298c37fd
Thread-0:singleton.Lazy.LazySimpleSingleton@6ebc1cfd

可以看到 產生的兩個實例的內存地址不同說明產生了兩個實例,大家可以通過以下打斷點的方式實現不同Thread運行狀態見進行切換。

  要解決線程問題第一反應是加 synchronized 加在創建實例的地方:public static synchronized LazySimpleSingleton getInstance(),但當線程數量較多時,用Synchronized加鎖,會使大量線程阻塞,就需要更好的解決辦法:

    public static LazySimpleSingleton getInstance() {
        if (lazy == null) {
            synchronized (LazySimpleSingleton.class) { if (lazy == null) { lazy = new LazySimpleSingleton(); } }
        }
        return lazy;
    }

  synchronized (lock) lock這個對象就是 “鎖”,當兩個並行的線程a,b,當a先進入同步塊,即a先拿到lock對象,這時候a就相當於用一把鎖把synchronized里面的代碼鎖住了,現在只有a才能執行這塊代碼,而b就只能等待a用完了lock對象鎖之后才能進入同步塊。但是用到 synchronized 總歸是要上鎖的,對性能還是有影響,那就用這種方式:用內部類的方式進行懶加載。

public class LazyInnerClassSingleton {
    private LazyInnerClassSingleton() {
    }

    private static final LazyInnerClassSingleton getIngestance() {
        return LazyHolder.LAZY;
    }

    private static class LazyHolder {
        private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

  內部類在LazyInnerClassSingleton類加載時加載,解決了餓漢式的性能問題,LazyInnerClassSingleton在內部類加載時,getIngestance()方法被調用之前實例化,解決了線程不安全問題。

 四、反射破壞單例

public class LazyInnerClassSingletonTest {
    public static void main(String[] args) {
        try {
            Class<?> clazz = LazyInnerClassSingleton.class;
            //通過反射回去私有構造方法
            Constructor constructor = clazz.getDeclaredConstructor(null);
            //強制訪問
            constructor.setAccessible(true);
            //暴力初始化
            Object o1 = constructor.newInstance();
            //創建兩個實例
            Object o2 = constructor.newInstance();
            System.out.println("o1:" + o1);
            System.out.println("o2:" + o2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

結果:

o1:singleton.Lazy.LazyInnerClassSingleton@1b6d3586
o2:singleton.Lazy.LazyInnerClassSingleton@4554617c

創建了兩個實例,違反了單例,現在在構造方法中做一些限制,使得多次重復創建時,拋出異常:

 private LazyInnerClassSingleton() {
        if (LazyHolder.class != null) {
            throw new RuntimeException("不允許創建多個實例");
        }
    }

這應該就是最好的單例了,哈哈哈。

五、注冊式單例模式

  注冊式單例模式又稱為登記式單例模式,就是將每個實例都登記到某個地方,使用唯一標識獲取實例。注冊式單例模式有兩種:枚舉式單例模式、容器式單例模式。注冊式單例模式主要解決通過反序列化破壞單例模式的情況。

1.枚舉式單例模式 

public enum EnumSingleton {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void steData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance() {
        return INSTANCE;
    }
}

測試代碼:

public class EnumSingletonTest {
    public static void main(String[] args) {
        try {
            EnumSingleton instance1 = EnumSingleton.getInstance();
            EnumSingleton instance2 = null;
            instance1.steData(new Object());

            FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(instance1);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("EnumSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            instance2 = (EnumSingleton) ois.readObject();
            ois.close();

            System.out.println(instance1.getData());
            System.out.println(instance2.getData());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

結果:

java.lang.Object@568db2f2
java.lang.Object@568db2f2

  那枚舉式單例是如何解決反序列化得問題呢?

  通過反編譯,可以在EnumSingleton.jad文件中發現static{} 代碼塊,枚舉式單例模式在靜態代碼塊中給INSTANCE進行了賦值,是餓漢式單例模式的實現。查看JDK源碼可知,枚舉類型其實通過類名和類對象找到一個唯一的枚舉對象。因此,枚舉對象不可能被類加載器加載多次。

  當你試圖用反射破壞單例時,會報 Cannot reflectively create enum objects ,即不能用反射來創建枚舉類型。進入Customer的newInstance(),其中有判斷:如果修飾符是Modifier.ENUM,則直接拋出異常。JDK枚舉的語法特殊性及反射也為美劇保駕護航,讓枚舉式單例模式成為一種比較優雅的實現。

2.容器式單例

public class ContainerSingleton {
    private ContainerSingleton() {
    }

    private static Map<String, Object> ioc = new ConcurrentHashMap<>();

    public static Object getBean(String className) {
        synchronized (ioc) {
            if (!ioc.containsKey(className)) {
                Object o = null;
                try {
                    o = Class.forName(className).newInstance();
                    ioc.put(className, o);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return o;
            } else {
                return ioc.get(className);
            }
        }
    }
}

spring中使用的就是容器式單例模式。

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM