1. 程式人生 > >並發編程與高並發學習筆記二

並發編程與高並發學習筆記二

空間 ring 調用 類加載 靜態初始化 urn 單例模式 inner mil

發布安全對象
一,發布對象
1.發布對象:是一個對象能夠被當前範圍之外的代碼所使用
2.對象逸出:一種錯誤的發布。當一個對象還沒有構造完成時,就使他被其他線程所見
//發布對象,這是一個不安全的對象
public class UnsafePublish {

    private String[] states = {"a","b","c"};

    public String[] getStates() {
        return states;
    }

    public static void main(String[] args) {
        UnsafePublish unsafePublish 
= new UnsafePublish(); System.out.println(unsafePublish.getStates()); //這裏我修改了數組裏的一個值 //所以這是一個不安全的對象,我們無法保證另外一個線程會不會修改對象裏的屬性的值 unsafePublish.getStates()[0] = "d"; System.out.println(unsafePublish.getStates()); } }


//對象逸出,(在一個對象未完成創建之前,不可以將其發布)
public class Escape {
    
private int thisCanBeEscape = 0; public Escape(){ new InnerClass(); } /** * 內部類的實例裏面包含了對包裝實例的引用,在對象沒有被構造完成之前,就會被發布。 * 有可能存在不安全的因素。一個導致this引用在構造期間溢出的錯誤,他是在構造函數中 * 相當於啟動了一個新的線程,無論是隱式的啟動還是顯示的啟動,都會造成this引用的逸出。 * 新線程會在所屬對象構造完畢之前就已經看到它(this)了, * 所以要在構造函數中創建線程,那麽不要啟動它。而是采用一個專有的start,或初始化的方法, * 來統一啟動線程。可以采用工廠方法和私有構造函數來完成對象的創建和監聽器的註冊等等 * 這樣才可以避免不正確的創建 * *
*/ private class InnerClass{ public InnerClass(){ System.out.println(Escape.this.thisCanBeEscape); } } public static void main(String[] args) { new Escape(); } }


,安全的發布對象
1.在靜態初始化函數中初始化一個對象的引用
2.將對象的引用保存到volatile類型域或者AtomicReference對象中
3.將對象的引用保存到某個正確構造對象的final類型域中
4.將對象的引用保存到一個由鎖保護的域中
三,創建單例的方法
1.餓漢模式
/**
 * 餓漢模式
 * 單例實例在類裝載時創建
 */
//線程安全的
/*
    缺點:如果構造函數中存在過多的處理,會導致類加載時非常慢,因此會引起性能問題
         如果加載類後並沒有調用,會造成資源的浪費
 */
public class SingletonHungry {
    //私有構造函數
    private SingletonHungry(){
    }
    //單例對象
    private static SingletonHungry instance = new SingletonHungry();
    //靜態工廠方法
    public static SingletonHungry getInstance(){
        return instance;
    }
}

2.餓漢模式優化

/**
 * 餓漢模式
 * 單例實例在類裝載時創建
 */
//線程安全的
/*
    缺點:如果構造函數中存在過多的處理,會導致類加載時非常慢,因此會引起性能問題
         如果加載類後並沒有調用,會造成資源的浪費
 */
public class SingletonHungry2 {
    //私有構造函數
    private SingletonHungry2(){
    }
    //單例對象
    private static SingletonHungry2 instance = null;
    /*
        使用靜態塊來初始化對象,
        註意:private static SingletonHungry2 instance = null; 一定要在前面
     */
    static {
        instance = new SingletonHungry2();
    }
    //靜態工廠方法
    public static SingletonHungry2 getInstance(){
        return instance;
    }
}

3.懶漢模式1

/**
 * 懶漢模式
 * 單例實例在第一次使用時創建
 */
//線程不安全的
public class SingletonLazy {
    //私有化構造函數
    private SingletonLazy(){
    }
    //定義單例對象
    public static SingletonLazy instance = null;
    //靜態工廠方法
    public static SingletonLazy getInstance(){
        /**
         * 單線程中這種方法沒問題,
         * 但是在多線程中,當兩個線程同時判斷instance == null時,會創建兩個不同的實例
         * 如果我們在構造方法中什麽也不做,就算兩個對象不同,似乎也不會出什麽問題啊
         * 但是如果我們在構造方法中做一些運算的操作,那麽創建兩次對象,運算的結果就會出錯
         *
         */
        if(instance == null){
            instance = new SingletonLazy();
        }
        return instance;
    }
}

4.懶漢模式2

/**
 * 懶漢模式
 * 單例實例在第一次使用時創建
 */
//線程安全的,但是不推薦這種方法
public class SingletonLazy2 {
    //私有化構造函數
    private SingletonLazy2(){
    }
    //定義單例對象
    private static SingletonLazy2 instance = null;
    //靜態工廠方法
    /*
        添加了synchronized後,該方法在同一時間只允許一個線程訪問,可以保證線程安全.
        但是不推薦這種方法,這種方法帶來了性能上的開銷
     */
    public static synchronized SingletonLazy2 getInstance(){
        /**
         * 單線程中這種方法沒問題,
         * 但是在多線程中,當兩個線程同時判斷instance == null時,會創建兩個不同的實例
         * 如果我們在構造方法中什麽也不做,就算兩個對象不同,似乎也不會出什麽問題啊
         * 但是如果我們在構造方法中做一些運算的操作,那麽創建兩次對象,運算的結果就會出錯
         *
         */
        if(instance == null){
            instance = new SingletonLazy2();
        }
        return instance;
    }
}

5.懶漢模式3

/**
 * 懶漢模式 -->雙重同步鎖單例模式
 * 單例實例在第一次使用時創建
 */
/*
    也是線程不安全的
 */
public class SingletonLazy3 {
    //私有化構造函數
    private SingletonLazy3(){
    }
    /**
     *  分析:instance = new SingletonLazy3();操作其實是分三步完成的
     *  1.分配對象的內存空間, memory = allocate();
     *  2.初始化對象 ,ctorInstance();
     *  3.設置instance指向剛分配的內存, instance = memory
     *  在單線程情況下,是沒有問題的
     *  但是在多線程情況下,由於JVM和cpu優化,會發生指令重排。執行順序發生了變化。
         *  1.分配對象的內存空間, memory = allocate();
         *  2.設置instance指向剛分配的內存, instance = memory
         *  3.初始化對象 ,ctorInstance();
     *  有可能會出現問題的情況:
     *      當線程A執行到instance = new SingletonLazy3();時,執行到了第二步:設置instance指向剛分配的內存
     *      線程B執行到第一個instance == null,由於instance = memory,所以這時線程B直接返回instance
     *      但是,線程A還沒有執行步驟三:初始化對象。這就會有問題。
     *
     */

    //定義單例對象
    private static SingletonLazy3 instance = null;
    //靜態工廠方法
    public static SingletonLazy3 getInstance(){
        /*
            縮小synchronized作用範圍
            並使用雙重檢驗機制
         */
        if(instance == null){
            synchronized (SingletonLazy3.class){
                if (instance == null){
                    instance = new SingletonLazy3();
                }
            }
        }
        return instance;
    }
}

6.懶漢模式4

/**
 * 懶漢模式 -->雙重同步鎖單例模式+volatile
 * 單例實例在第一次使用時創建
 */
/*
    變成線程安全的
 */
public class SingletonLazy4 {
    //私有化構造函數
    private SingletonLazy4(){
    }
    //定義單例對象
    /*
        用volatile修飾,防止發生指令重排。這樣就是賢臣安全的了
     */
    private volatile static SingletonLazy4 instance = null;

    //靜態工廠方法
    public static SingletonLazy4 getInstance(){
        /*
            縮小synchronized作用範圍
            並使用雙重檢驗機制
         */
        if(instance == null){
            synchronized (SingletonLazy4.class){
                if (instance == null){
                    instance = new SingletonLazy4();
                }
            }
        }
        return instance;
    }
}

7.枚舉模式

/**
 * 枚舉模式 --最安全
 * 實際使用時才初始化對象,
 */
//線程安全的,推薦這種。
public class SingletonEnum {
    //私有構造方法
    private SingletonEnum(){
    }

    public static SingletonEnum getInstance(){
        return Singleton.INSTANCE.getSingleton();
    }
    private enum Singleton{
        INSTANCE;
        private SingletonEnum singleton;
        //JVM保證這個方法絕對只被調用一次
        Singleton(){
            singleton = new SingletonEnum();
        }
        public SingletonEnum getSingleton(){
            return singleton;
        }
    }
}

並發編程與高並發學習筆記二