最近剛看了單例模式,由於以前只在大話上看過就兩種簡單實現,也把網上介紹的給實踐一下,而後對比一下JDK裏面有類似的實現,簡單講一下個人理解,歡迎批評哈:java
餓漢模式:安全
1 public class Singleton { 2 3 private static final Singleton instance = new Singleton(); 4 5 private Singleton() { 6 } 7 8 public static Singleton getInstance() { 9 return instance; 10 } 11 12 13 } 14
優勢:簡單直觀,線程安全,在類加載的時候就直接初始化了ide
缺點:寫死的方式不夠靈活,有多個classload的時候也會產生多個實例,並且不夠lazy loading。函數
JDK的例子(java.lang.Runtime):測試
1 public class Runtime { 2 private static Runtime currentRuntime = new Runtime(); 3 4 public static Runtime getRuntime() { 5 return currentRuntime; 6 } 7 8 /** Don't let anyone else instantiate this class */ 9 private Runtime() {}
下面基於類加載機制寫了個簡單的demo看下這種餓漢模式存在的問題(代碼稍作修改):this
參考:spa
http://my.oschina.net/pingpangkuangmo/blog/376328.net
1 public class Singleton { 2 3 private static final Singleton instance = new Singleton(); 4 5 private Singleton() { 6 System.out.print("類加載:"); 7 System.out.println(this.getClass().getClassLoader()); 8 } 9 10 public static Singleton getInstance() { 11 return instance; 12 } 13 14 }
下面仿照網上的例子寫了個簡單的類加載器:線程
1 import java.io.ByteArrayOutputStream; 2 import java.io.FileInputStream; 3 4 public class MyclassLoader extends ClassLoader { 5 6 private String name; 7 private String classPath; 8 9 public MyclassLoader(String name){ 10 super(null); 11 this.name = name; 12 } 13 14 @Override 15 public Class<?> findClass(String name){ 16 byte[] by=getClassBytes(name); 17 return this.defineClass("service.Singleton",by,0,by.length); 18 } 19 20 private byte[] getClassBytes(String name) { 21 byte[] data = null; 22 String classFullPath=name; 23 try { 24 FileInputStream in=new FileInputStream(classFullPath); 25 ByteArrayOutputStream out=new ByteArrayOutputStream(); 26 int i = 0; 27 while ((i = in.read()) != -1) { 28 out.write(i); 29 } 30 data = out.toByteArray(); 31 } catch (Exception e) { 32 e.printStackTrace(); 33 } 34 return data; 35 } 36 37 public String getName() { 38 return name; 39 } 40 41 public void setName(String name) { 42 this.name = name; 43 } 44 45 public String getClassPath() { 46 return classPath; 47 } 48 49 public void setClassPath(String classPath) { 50 this.classPath = classPath; 51 } 52 }
下面是測試程序:3d
1 public static void main(String[] args) { 2 Singleton singleton_1 = Singleton.getInstance(); 3 Singleton singleton_2 = Singleton.getInstance(); 4 System.out.println("正常類加載單例實例比較:"+"{"+singleton_1+":"+singleton_2+""+"}-->"+singleton_1.equals(singleton_2)); 5 System.out.println("正常加載類信息比較:"+"{"+singleton_1.getClass()+":"+singleton_2.getClass()+""+"}-->"+singleton_1.getClass().equals(singleton_2.getClass())); 6 MyclassLoader myClassLoader1=new MyclassLoader("myClassLoader1"); 7 MyclassLoader myClassLoader2=new MyclassLoader("myClassLoader2"); 8 9 Class singletonClass1 = myClassLoader1.findClass("H:/totalwork/exerse/target/classes/service/Singleton.class"); 10 Class singletonClass2 = myClassLoader2.findClass("H:/totalwork/exerse/target/classes/service/Singleton.class"); 11 12 System.out.println("單獨加載器類信息:"+"{"+singletonClass1+":"+singletonClass2+""+"}-->"+singletonClass1.equals(singletonClass2)); 13 14 try { 15 Constructor constructor_1=singleton_1.getClass().getDeclaredConstructor(); 16 Constructor constructor_2=singleton_2.getClass().getDeclaredConstructor(); 17 System.out.println("正常類加載單例構造器比較:"+"{"+constructor_1+":"+constructor_2+""+"}-->"+constructor_1.equals(constructor_2)); 18 19 Constructor constructor1=singletonClass1.getDeclaredConstructor(); 20 Constructor constructor2=singletonClass2.getDeclaredConstructor(); 21 System.out.println("單獨加載器構造器比較:"+"{"+constructor1+":"+constructor2+""+"}-->"+constructor1.equals(constructor2)); 22 23 System.out.println("-----------設置權限:start------------"); 24 constructor_1.setAccessible(true); 25 constructor_2.setAccessible(true); 26 System.out.println("-----------設置權限:end--------------"); 27 28 Object obj_1=constructor_1.newInstance(); 29 Object obj_2=constructor_2.newInstance(); 30 System.out.println("正常類加載單例生成實例比較:"+"{"+obj_1.hashCode()+":"+obj_2.hashCode()+""+"}-->"+obj_1.equals(obj_2)); 31 32 System.out.println("-----------設置權限:start------------"); 33 constructor1.setAccessible(true); 34 constructor2.setAccessible(true); 35 System.out.println("-----------設置權限:end--------------"); 36 37 Object obj1=constructor1.newInstance(); 38 Object obj2=constructor2.newInstance(); 39 40 System.out.println("單獨加載器單例生成實例比較:"+"{"+obj1+":"+obj2+""+"}-->"+obj1.equals(obj2)); 41 42 } catch (Exception e) { 43 e.printStackTrace(); 44 } 45 46 }
下面是運行結果:
1 類加載:sun.misc.Launcher$AppClassLoader@fb56b1 2 正常類加載單例實例比較:{service.Singleton@290fbc:service.Singleton@290fbc}-->true 3 正常加載類信息比較:{class service.Singleton:class service.Singleton}-->true 4 單獨加載器類信息:{class service.Singleton:class service.Singleton}-->false 5 正常類加載單例構造器比較:{private service.Singleton():private service.Singleton()}-->true 6 單獨加載器構造器比較:{private service.Singleton():private service.Singleton()}-->false 7 -----------設置權限:start------------ 8 -----------設置權限:end-------------- 9 類加載:sun.misc.Launcher$AppClassLoader@fb56b1 10 類加載:sun.misc.Launcher$AppClassLoader@fb56b1 11 正常類加載單例生成實例比較:{13228332:30472956}-->false 12 -----------設置權限:start------------ 13 -----------設置權限:end-------------- 14 類加載:service.MyclassLoader@1bf3d87 15 類加載:service.MyclassLoader@1bf3d87 16 類加載:service.MyclassLoader@1e4f7c2 17 類加載:service.MyclassLoader@1e4f7c2 18 單獨加載器單例生成實例比較:{service.Singleton@10dc6b5:service.Singleton@170bea5}-->false
一、能夠看到MyclassLoader是咱們本身的類加載器,自定義classloader咱們須要經過繼承java.lang.ClassLoader
來實現,其中private byte[] getClassBytes(String name)函數,就是經過文件流傳輸講.class字節碼讀到byte[] data中。
二、而後交給classloader的defineClass1來實現加載,從源碼能夠看出來
1 protected final Class<?> defineClass(String name, byte[] b, int off, int len) 2 throws ClassFormatError 3 { 4 return defineClass(name, b, off, len, null); 5 }
...
1 private native Class defineClass1(String name, byte[] b, int off, int len, 2 ProtectionDomain pd, String source);
其中:
一、singleton_1和singleton_2爲直接產生的單例對象,咱們也能夠看到指向同一個對象。也能夠看到他們默認爲sun.misc.Launcher$AppClassLoader@fb56b1加載器加載。
二、myClassLoader1和myClassLoader2是咱們構造的兩個類加載器。
三、singletonClass1和singletonClass2分別是兩個構造器構造的Class對象。其實對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立其在java虛擬機的惟一性,每個類加載器,都擁有一個獨立的類命名空間。通俗表達:比較兩個類是否「相等」,只有在這兩個類由同一個類加載器加載的前提下才有意義,不然,即便這兩個類來源於同一個class文件,被同一個虛擬機加載,只要加載它們的類加載器不一樣,那這兩個類一定不相等(摘自深刻理解Java虛擬機)。
經過上面的解釋,下面幾個對象比較的結果其實就能夠理解了。
四、constructor_1和constructor_2爲單例對象class獲得的,constructor1和constructor2爲加載類獲得的。
其實有兩種方法能夠執行類的構造函數:
Class.newInstance() 只可以調用無參的構造函數,即默認的構造函數, 不過要求構造函數爲public,上面的單例會直接拋出java.lang.IllegalAccessException異常。
Constructor.newInstance() 能夠根據傳入的參數,調用任意構造構造函數。
五、從結果來看constructor_1和constructor_2是equal的,constructor1和constructor2壓根沒有可比性,因此確定爲false。
先寫這麼多,回頭再補充吧。
這裏還有幾個疑問沒解決,知道的但願幫我解答一下:
一、constructor_1和constructor_2分別new出來的實例obj_1和obj_2爲何不相等?我查看了一下它們倆的hashCode,確實不同,可是它們兩個是同一個系統類加載器加載的,有點搞不懂。
二、Object obj1=constructor1.newInstance();這個語句執行了兩次無參構造函數,我單步發現obj1生成的過程當中,先執行了無參構造函數再執行的靜態成員初始化語句
private static final Singleton instance = new Singleton();誰能告訴我緣由呢?
感謝網友回答:對於第一個問題,這兩個對象本應該就是不同的,一旦能用反射,就沒有單例可言了,使用枚舉能夠阻止反射。
對於第二個問題,就是在加載類的時候就會去執行static方法,執行的時候發現是new 自己,就去執行構造函數了。類加載完成以後,緊接着執行constructor3.newinstance方法,又會執行一次構造方法,因此會執行兩次。因此對於系統默認類加載器只是把靜態成員初始化了而已,沒有再像newinstance這樣調用構造函數。