woff 發表於 2018-8-29 22:46:43

java的getInstance() 如何使用?

這是Singleton pattern.但是現在已經不建議這樣寫了。首先這沒有被lazy initialization,在程序啟動時(class Smarty被load)就會創建你的static instance,拖慢啟動速度。要解決lazy initialization,就得這麼寫:public class Smarty {
    private static Smarty instance;
    private Smarty() {}
    public static Smarty getInstance() {
      if(instance == null)
            instance = new Smarty();
      return instance;
    }
}
好了,現在instance只會在第一次getInstance被call的時候被創建。但是不好意思,多線程的情況下咋辦?恩,似乎應該這樣:public class Smarty {
    private static Smarty instance;
    private Smarty() {}

    public synchronized static Smarty getInstance() {
      if(instance == null)
            instance = new Smarty();
      return instance;
    }
}
看起來不錯了,但是每次調用 getInstance()都需要synchronized,似乎還可以改進:public class Smarty {
    private static Smarty instance;
    private Smarty() {}

    public static Smarty getInstance() {
      if(instance == null) {
            synchronized(Smarty.class) {
                instance = new Smarty();
            }
      }
      return instance;
    }
}
這樣行了嗎?好像可以了,但是有個bug,十個線程都進行了null check,但都還沒進入synchonized block,大家一起排隊等著吃果果。於是十個Smarty排著隊被產出來了。不行。那這樣呢:public class Smarty {
    private static Smarty instance;
    private Smarty() {}

    public static Smarty getInstance() {
      if(instance == null) {
            synchronized(Smarty.class) {
                if(instance == null) {
                  instance = new Smarty();
                }
            }
      }
      return instance;
    }
}
你們不是排隊等著進來吃果果嗎?進來了我再來check一次,這下不可能產出10個了吧。看起來已經完美了,可惜還是不行。這個可能不太好理解,但是下面這一行不是atomic的操作,它實際被分成幾個步驟:allocate memory, assign allocated memory to instance ref, initialize the object Smarty into the allocated memory.instance = new Smarty();
所以說如果線程1卡在第三步那裡,線程2高高興興滴進來拿instance了,他會拿到一個還沒煮好的蛋,吃下去,完蛋了。有個keyword,可能一般你很少用到,叫volatile:public class Smarty {
    private static volatile Smarty instance;
    private Smarty() {}

    public static Smarty getInstance() {
      if(instance == null) {
            synchronized(Smarty.class) {
                if(instance == null) {
                  instance = new Smarty();
                }
            }
      }
      return instance;
    }
}
volatile是什麼鬼?看看定義:What is the Java volatile keyword?Essentially, volatile is used to indicate that a variable's value will be modified by different threads .Declaring a volatile Java variable means:The value of this variable will never be cached thread-locally : all reads and writes will go straight to "main memory";Access to the variable acts as though it is enclosed in a synchronized block , synchronized on itself.第二點,正是我們需要的,我還沒煮完的雞蛋你先別拿。恩,終於可以了。但是,據說大部分JVM的implementation並不尊重volatile的規則。完蛋啦,搞了半天還是沒法確定可以完美做singleton。別緊張,這樣寫就沒問題了。正確的Java Singleton寫法:public class Smarty {
    private Smarty() {}
   
    private static class SmartyLoader {
      private static final Smarty instance = new Smarty();
    }

    public static Smarty getInstance() {
      return SmartyLoader.instance;
    }
}
因為Inner class的loading是在它第一次被調用的時候。合著整了半天就是這麼簡單。還有一種寫法,Java 5之後可用:public enum Smarty {
    INSTANCE;
}
就是這麼簡單粗暴直接。


Java實現單例需要注意兩點一是盡量實現惰性加載二是線程安全而單例的最基本要求就是控制實例化過程所以必須將構造函數私有化也就是不允許其它任何地方調用構造函數實例化出一個對象來題主提的寫法是飽漢式的寫法即類加載的時候就加載實例化出對象來,並且一定要用private控制私有性這麼想如果這個單例你一直不用但是加載時直接實例化了就佔用了一塊內存空間這麼看來是不划算的,所以在用的時候在實例化這樣就叫惰性加載或延遲加載這時候就會涉及到線程安全問題飽漢式簡單就在於在類加載時直接實例化這樣線程安全就交給了類加載過程JVM來控制線程安全所以工業界寫法一般都是雙重檢驗鎖或者靜態內部類雙重檢驗鎖通過Syncronized的關鍵字來控制線程安全問題class Singleton{
    private volatile static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
      if(instance == null){
            synchronized(Singleton.class){
                if(instance == null){
                  instance = new Singleton();
                }
            }
      }
      return instance;
    }
}
其中註意volatile的用處保持變量的可見性(修正)及禁止指令重排序第一重判斷只在單例未實現時判斷並且假設多個線程通過了第一個if 這時候他們進入syncronized關鍵字控制的同步代碼塊中,只有一個線程獨占,這時候它判斷如果instance還是null的話就初始化,這之後它退出同步代碼塊,剛才同時跟之前那個哥們一起等待線程使用權的線程們,再次檢查第二重判斷的時候instance就不為Null了這時直接返回,這樣就保證了線程安全,至此過程結束靜態內部類方法class Singleton{
    private static class SingletonHolder{
      private static final Singleton instance = new Singleton();
    }// final 其实可有可无
    private Singleton(){}
    public static Singleton getInstance(){
      return SingletonHolder.instance;
    }
}
這種方法是由類加載過程JVM控制線程安全的,調用return SingletonHolder.instance時,INSTANCE在第一次調用時實例化(如果想徹底一些了解,去了解內部類)至此結束然後Effect Java中推薦的是enum枚舉實現單例這就涉及到類的序列化和防止通過反射攻擊的問題了書中的觀點是枚舉實現的方式是最容易的,同時也是最佳的,具體需要深入了解java的枚舉enum Singleton{
   INSTANCE;
   Singelton(){
   }
}
// INSTANCE 在第一次被调用时初始化,
就這些
https://www.zhihu.com/question/29971746
頁: [1]
查看完整版本: java的getInstance() 如何使用?