什麼是單例模式?java
單例模式是一種經常使用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例類的特殊類。經過單例模式能夠保證系統中一個類只有一個實例並且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。若是但願在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。設計模式
單例模式的特色:緩存
一、單例類有且只能有一個實例。安全
二、單例類須要本身建立一個本身的實例。併發
三、單例類須要爲其餘類提供這個實例。高併發
哪些地方常常用到單例?測試
在計算機系統中,配置文件,線程池,緩存,日誌對象,打印機等常常用到單例模式spa
所謂「一山不容二虎」,若是出現二虎很容易會出問題,好比配置文件,它終究只是一個文件,若是同時有好幾個實例訪問它並執行修改操做,那麼這時候就會引起出一系列的問題 線程
單例相對於多實例對象也更節約系統資源設計
單例模式經常使用的有幾種模式?
通常來說單例模式有三種,分別是:懶漢式,餓漢式,登記式。
下面直接上代碼說明吧
①一個類之因此可以建立出實例是由於有構造方法的存在,只要咱們把構造方法的訪問修飾符改爲私有(private),外界就不能經過new來建立該類的實例。
②在單例類中自身new出一個對象,由於要被外界訪問,咱們能夠把它靜態化(static),以便外界訪問(類型.對象)。
③有時候咱們須要控制這個對象,也處於安全起見,咱們能夠把繼續私有化(private),而後提供一個getter方法以便外界訪問。
SimpletonDemo1.java(單例類)
1 package com.lcw.simpleton; 2 3 public class SimpletonDemo1 { 4 //將構造方法私有化,阻止外界直接建立對象 5 private SimpletonDemo1() { 6 } 7 //提供static以便外界訪問 8 private static SimpletonDemo1 instance = new SimpletonDemo1(); 9 10 //提供getter方法以便外界訪問私有化對象,static SimpleDemo1返回類型 11 public static SimpletonDemo1 getInstance() { 12 return instance; 13 } 14 }
SimpletonTest.java(測試類)
1 package com.lcw.simpleton; 2 3 public class SimpletonTest { 4 public static void main(String[] args) { 5 SimpletonDemo1 s1=SimpletonDemo1.getInstance(); 6 SimpletonDemo1 s2=SimpletonDemo1.getInstance(); 7 8 if(s1==s2){//檢測對象的內存地址是否一致 9 System.out.println("s1和s2是同個對象"); 10 }else{ 11 System.out.println("s1和s2不是同個對象"); 12 } 13 14 } 15 }
效果以下:
上面所說的就是單例模式裏餓漢模式,爲何叫餓漢模式呢?
因爲這個實例是被static所修飾,被static修飾的成員屬於類全部,當類加載的時候,這個成員就被加載了,也就是說無論外界是否調用這個類,它都已經被加載了。
看起來好像是餓漢,無論三七二十一,先吃了你再說。
下面再來看下單例模式中的懶漢模式
就字面上的意思,其實已經很明白了「懶漢模式」,顧名思義不一樣於餓漢模式,既然餓漢模式是無論三七二十一先吃了再說,那麼懶漢模式固然就沒那麼勤快了,應該是被咱們調用後的時候纔去實例化對象。
它們的寫法很相似,只不過是在用private static聲明對象的時候不直接new對象,而是在gette方法裏再去實例化對象
而後判斷下這個對象是否爲null,若是爲null則實例化一個對象,若是不會空則直接返回對象。
下面看下具體代碼
SimpletonDemo2.java(單例類)
1 package com.lcw.simpleton; 2 3 public class SimpletonDemo2 { 4 //將構造方法私有化,阻止外界直接建立對象 5 private SimpletonDemo2() { 6 } 7 //提供static以便外界訪問 8 private static SimpletonDemo2 instance; 9 10 //提供getter方法以便外界訪問私有化對象,static SimpleDemo1返回類型 11 public static SimpletonDemo2 getInstance() { 12 if(instance==null){ 13 return instance=new SimpletonDemo2(); 14 }else{ 15 return instance; 16 } 17 } 18 }
SimpletonTest.java(測試類)
1 package com.lcw.simpleton; 2 3 public class SimpletonTest { 4 public static void main(String[] args) { 5 SimpletonDemo1 s1=SimpletonDemo1.getInstance(); 6 SimpletonDemo1 s2=SimpletonDemo1.getInstance(); 7 8 if(s1==s2){//檢測對象的內存地址是否一致 9 System.out.println("s1和s2是同個對象"); 10 }else{ 11 System.out.println("s1和s2不是同個對象"); 12 } 13 14 15 SimpletonDemo2 s3=SimpletonDemo2.getInstance(); 16 SimpletonDemo2 s4=SimpletonDemo2.getInstance(); 17 18 if(s3==s4){//檢測對象的內存地址是否一致 19 System.out.println("s3和s4是同個對象"); 20 }else{ 21 System.out.println("s3和s4不是同個對象"); 22 } 23 24 } 25 }
效果以下:
總結下兩種模式的區別:
一、餓漢式,在加載類的時候比較慢,因爲它還要去實例化一個對象並形成內存資源的浪費,但在運行調用中的速度會比較快。
二、懶漢式,在加載類的時候比較快,因爲在加載類的時候不須要去實例化對象,但在運行調用時的速度比較慢,因爲還要去作判斷。
還有一點很重要的是,餓漢模式是屬於線程安全,而懶漢模式屬於線程不安全,在高併發時會出現問題。
那有沒有更好的方式呢?答案確定是有的,咱們能夠結合了餓漢式和懶漢式各自的優勢,不在加載類的時候實例化沒必要要的對象,又同時具有了線程安全
說直白的點就是利用內部類去靜態實例化這個對象,因爲內部類須要在外部類被調用的時候纔會去加載,也就是說不會在不必的時候去加載對象從而致使系統資源的浪費,同時咱們在內部類中對這個對象實行靜態實例化也就避免了線程安全這個問題。
1 package com.lcw.simpleton; 2 3 public class SimpletonDemo { 4 // 將構造方法私有化,阻止外界直接建立對象 5 private SimpletonDemo() { 6 } 7 8 // 定義一個內部類用來實例化對象 9 private static class Inner_SimpletonDemo { 10 private static SimpletonDemo instance = new SimpletonDemo(); 11 } 12 13 // 提供getter方法以便外界訪問私有化對象,static SimpleDemo1返回類型 14 public static SimpletonDemo getInstance() { 15 return Inner_SimpletonDemo.instance; 16 } 17 }
再來看下最後一種實現方式,登記式
因爲懶漢式和餓漢式都把構造方法私有化了,因此它不能被繼承,登記式能夠解決這個問題
登記式單例模式 相似於Spring裏面的用法,將類名註冊並放到Map集合裏,下次要用的時候直接取
登記式實際對一組單例模式進行的維護,主要是在數量上的擴展,經過map咱們把單例存進去,這樣在調用時,先判斷該單例是否已經建立,是的話直接返回,不是的話建立一個登記到map中,再返回。對於數量又分爲固定數量和不固定數量的。下面採用的是不固定數量的方式,在getInstance方法中加上參數(string name),而後經過子類繼承,重寫這個方法將name傳進去。
SimpletonDemo3.java(單例類)
1 package com.lcw.simpleton; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 public class SimpletonDemo3 { 7 // 私有化構造器,保護鉤子 8 private SimpletonDemo3() { 9 } 10 11 private static Map<String, SimpletonDemo3> map = new HashMap<String, SimpletonDemo3>(); 12 //靜態代碼塊 13 static { 14 SimpletonDemo3 instance = new SimpletonDemo3(); 15 map.put(instance.getClass().getName(), instance); 16 } 17 18 // 參數name爲類名 19 public static SimpletonDemo3 getInstance(String name) { 20 if (name == null) { 21 name = "com.lcw.simpleton.SimpletonDemo3"; 22 } 23 if (!map.containsKey(name)) { 24 try { 25 map.put(name, (SimpletonDemo3) Class.forName(name) 26 .newInstance()); 27 } catch (InstantiationException e) { 28 e.printStackTrace(); 29 } catch (IllegalAccessException e) { 30 e.printStackTrace(); 31 } catch (ClassNotFoundException e) { 32 e.printStackTrace(); 33 } 34 } 35 return map.get(name); 36 } 37 38 }
SimpletonTest.java(測試類)
1 package com.lcw.simpleton; 2 3 public class SimpletonTest { 4 public static void main(String[] args) { 5 SimpletonDemo1 s1 = SimpletonDemo1.getInstance(); 6 SimpletonDemo1 s2 = SimpletonDemo1.getInstance(); 7 8 if (s1 == s2) {// 檢測對象的內存地址是否一致 9 System.out.println("s1和s2是同個對象"); 10 } else { 11 System.out.println("s1和s2不是同個對象"); 12 } 13 14 SimpletonDemo2 s3 = SimpletonDemo2.getInstance(); 15 SimpletonDemo2 s4 = SimpletonDemo2.getInstance(); 16 17 if (s3 == s4) {// 檢測對象的內存地址是否一致 18 System.out.println("s3和s4是同個對象"); 19 } else { 20 System.out.println("s3和s4不是同個對象"); 21 } 22 23 SimpletonDemo3 s5 = SimpletonDemo3.getInstance("com.lcw.simpleton.SimpletonDemo3"); 24 SimpletonDemo3 s6 = SimpletonDemo3.getInstance("com.lcw.simpleton.SimpletonDemo3"); 25 if (s5 == s6) {// 檢測對象的內存地址是否一致 26 System.out.println("s5和s6是同個對象"); 27 } else { 28 System.out.println("s5和s6不是同個對象"); 29 } 30 } 31 }
效果圖:
做者:Balla_兔子
出處:http://www.cnblogs.com/lichenwei/本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文連接。正在看本人博客的這位童鞋,我看你氣度不凡,談吐間隱隱有王者之氣,往後必有一番做爲!旁邊有「推薦」二字,你就順手把它點了吧,相得準,我分文不收;相不許,你也好回來找我!