1. 程式人生 > >二、單例模式之單例模式

二、單例模式之單例模式

單例模式建立方式有以下幾種方式:

  1. 餓漢模式
  2. 懶漢模式
  3. 註冊式模式
  4. 列舉式模式
  5. 序列化模式

 

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));
    }
}