1. 程式人生 > >java 的知識要多多了解。(下文為轉發)

java 的知識要多多了解。(下文為轉發)

single 技術分享 main ner 但是 gif 出現問題 sca 初始化

什麽是發布對象?

發布對象是指使一個對象能夠被當前範圍之外的代碼所使用

什麽是對象逸出?

對象逸出是一種錯誤的發布,指當一個對象還沒有構造完成時,就使它被其他線程所見

逸出-demo

技術分享圖片
@Slf4j
public class Escape {

    private int thisCanBeEscape = 0;

    public Escape() {
        new InnerClass();
    }

    private class InnerClass{
        public InnerClass() {
            log.info("{}", Escape.this.thisCanBeEscape);
        }
    }

    public static void main(String[] args) {
        new Escape();
    }

}
技術分享圖片

在此實例中Escape對象還沒有構造完成,就訪問了該對象的成員變量thisCanBeEscape,該類是線程不安全的,並且非常不推薦這麽寫。

不安全的發布-demo

技術分享圖片
@Slf4j
public class UnsafePublish {

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

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

    public static void main(String[] args) {
        UnsafePublish unsafePublish = new UnsafePublish();
        log.info("{}", Arrays.toString(unsafePublish.getStates()));

        unsafePublish.getStates()[0] = "d";
        log.info("{}", Arrays.toString(unsafePublish.getStates()));

    }
}
技術分享圖片

輸出為:[a,b,c]和[d,b,c] 這樣發布的對象為線程不安全的,因為無法保證其他線程是否會修改states域,從而造成狀態錯誤

安全發布對象的四種方法:

a、在靜態初始化函數中初始化一個對象引用

b、將對象的引用保存到volatile類型域或AtomicReference對象中

c、將對象的引用保存到某個正確構造對象的final類型域中

d、將對象的引用保存到一個由鎖保護的域中

如何安全的發布一個對象呢?

我們以對不同單例的實現,來說明一下安全發布對象的方法

單例-demo1

技術分享圖片
public class SingletonExample1 {

    //私有構造函數
    private SingletonExample1() {

    }
    //單例對象
    private static SingletonExample1 instance = null;

    //靜態的工廠方法
    public static SingletonExample1 getInstance() {

        //單線程沒有問題,多線程的時候會出現問題
        if (instance == null) {
            instance = new SingletonExample1();
        }

        return instance;
    }
}
技術分享圖片

此實例在單線程模式下沒有任何問題,但在多線程模式下,如兩個線程都同時運行到判斷instance==null時,就有可能new出兩個實例來,所以說這是線程不安全的,這也就是懶漢模式,此實例滿足了a條件,如果再加上d條件,在判斷是否為null時加鎖,就可以變為線程安全的

單例-demo2

技術分享圖片
public class SingletonExample2 {

    //餓漢模式不足,如果構造方法中有過多的處理,會導致類加載的時候特別慢
    //私有構造函數
    private SingletonExample2() {

    }
    //單例對象
    private static SingletonExample2 instance = new SingletonExample2();

    //靜態的工廠方法
    public static SingletonExample2 getInstance() {

        return instance;
    }
}
技術分享圖片

此demo是線程安全的,使用餓漢模式時需要註意兩點,一是此類肯定被使用(避免造成資源浪費),二是私有的構造方法中沒有過多的處理4

單例-demo3

技術分享圖片
public class SingletonExample3 {

    //私有構造函數
    private SingletonExample3() {

    }
    //單例對象
    private static SingletonExample3 instance = null;

    //靜態的工廠方法
    public static synchronized SingletonExample3 getInstance() {

        //單線程沒有問題,多線程的時候會出現問題
        if (instance == null) {
            instance = new SingletonExample3();
        }

        return instance;
    }
}
技術分享圖片

此demo即為demo1加鎖的情況,是線程安全的,但是並不推薦這麽寫,因為這樣雖然保證了線程安全,但在性能上有一定的開銷

單例-demo4

技術分享圖片
/**
 * 懶漢模式 雙重同步鎖單例模式
 * 單例實例在第一次使用時進行創建
 * 雙重檢測機制不一定線程安全,因為有指令重排的存在
 */
public class SingletonExample4 {

    //私有構造函數
    private SingletonExample4() {

    }


    //單例對象
    private static SingletonExample4 instance = null;

    //靜態的工廠方法
    public static  SingletonExample4 getInstance() {

        //單線程沒有問題,多線程的時候會出現問題
        if (instance == null) {  //雙重檢測機制

            //同步鎖
            synchronized (SingletonExample4.class) {
                if (instance == null) {
                    instance = new SingletonExample4();
                }
            }

        }

        return instance;
    }

}
技術分享圖片

此demo是懶漢模式的優化版本,但註意此demo不是線程安全的,因為有指令重排的存在,當執行instance=new SingletonExample4()時,cpu會執行三步操作:1、memory=allocate() 分配對象的內存空間 2、ctorInstance()初始化對象 3、instance = memory 設置instance指向剛分配的內存, 但是由於jvm和cpu的優化,會發生指令重排,如重排的結果變為1,3,2,在單線程的情況下沒有任何問題,但是在多線程的情況下就可能發生問題,如果此時A線程執行到instance=new SingletonExample4(),發生了指令重排,執行到了第二步的3,此時instance已經執行了該對象的內存,但是該對象還沒有初始化,如果在此時B線程正好執行到if(instance==null),此時該條件已經不成立,直接return,因為這個對象還沒有初始化,直接去使用這個對象就可能發生問題。

單例-demo5

技術分享圖片
/**
 * 懶漢模式 雙重同步鎖單例模式
 * 單例實例在第一次使用時進行創建
 * 雙重檢測機制不一定線程安全,因為有指令重排的存在
 */
public class SingletonExample5 {

    //私有構造函數
    private SingletonExample5() {

    }

    //1、memory = allocate()  分配對象的內存空間
    // 2、 ctorInstance() 初始化對象
    // 3、 instance = memeory 設置instance指向剛分配的內存


    //通過volatile和雙重檢測機制限制指令重排,volatile限制了代碼的寫操作
    //單例對象,通過volatile限制代碼發生指令重排
    private volatile static SingletonExample5 instance = null;

    //靜態的工廠方法
    public static SingletonExample5 getInstance() {

        //單線程沒有問題,多線程的時候會出現問題
        if (instance == null) {  //雙重檢測機制

            //同步鎖
            synchronized (SingletonExample5.class) {
                if (instance == null) {
                    instance = new SingletonExample5();
                }
            }

        }

        return instance;
    }
}
技術分享圖片

此demo是demo4的升級版,只要解決了指令重排問題,在上篇博客“線程安全性中”我們已經介紹了volatile可以限制代碼發生指令重排,此demo是線程安全的。

單例-demo6

技術分享圖片
/**
 * 餓漢模式
 * 單例實例在類裝載時進行創建
 */
@ThreadSafe
public class SingletonExample6 {

    //餓漢模式不足,如果構造方法中有過多的處理,會導致類加載的時候特別慢
    //私有構造函數
    private SingletonExample6() {

    }
    //單例對象   靜態域的初始化
    private static SingletonExample6 instance = null;

    //靜態塊方式
    static {
        instance = new SingletonExample6();
    }
    
    //靜態的工廠方法
    public static SingletonExample6 getInstance() {

        return instance;
    }

    public static void main(String[] args) {
        System.out.println(getInstance().hashCode());
        System.out.println(getInstance().hashCode());
    }
}
技術分享圖片

demo2是餓漢模式的靜態代碼域方式,此demo是餓漢模式的靜態代碼塊方式,此demo也是線程安全的

單例-demo7

技術分享圖片
/**
 * 枚舉模式:最安全
 * 相比於懶漢模式在安全性方面更容易保證
 * 相比於餓漢模式是在實際調用的時候才做最開始的初始化
 */
public class SingletonExample7 {
    
    private SingletonExample7() {

    }
    //靜態的工廠方法
    public static SingletonExample7 getInstance() {

        return Singleton.INSTANCE.getSingleton();
    }

    private enum Singleton{
        INSTANCE;
        private SingletonExample7 singleton;

        //JVM保證這個方法絕對只調用一次,且是在這個類調用之前初始化的
        Singleton() {
            singleton = new SingletonExample7();
        }

        public SingletonExample7 getSingleton() {
            return singleton;
        }
    }

}
技術分享圖片

此demo是我們最推薦的單例寫法,並且是線程安全的,它相比於懶漢模式在安全性方面更容易保證,相比於餓漢模式是在實際調用的時候才做最開始的初始化

java 的知識要多多了解。(下文為轉發)