你真能說清楚單例模式嗎?

你真能說清楚單例模式嗎?

碼神手記——資深攻城獅的私房筆記。微信公眾平臺/知乎/頭條/簡書同步發(fā)文。感謝關注與轉發(fā)。

單例模式.PNG

核心作用

保證一個類只有一個實例,并且提供一個訪問該實例的全局訪問點。

常見應用場景

  • Windows的Task Manager(任務管理器)是很典型的單例模式
  • Windows的Recycle Bin(回收站)是單例應用。在系統(tǒng)運行中,回收站只維護僅有的一個實例。
  • 項目中,讀取配置文件的類,一般也只有一個對象。沒有必要每次使用配置文件數(shù)據(jù)都new一個對象去讀取。
  • 網(wǎng)站的計數(shù)器,一般也是采用單例模式實現(xiàn),否則難以同步。
  • 應用程序的日志應用,一般都使用單例模式實現(xiàn),一般因為共享的日志文件一直處于打開狀態(tài),只能有一個實例去操作,否則內(nèi)容不好追加。
  • 數(shù)據(jù)庫連接池的設計一般也采用單例模式。
  • 操作系統(tǒng)的文件系統(tǒng),也是單例模式實現(xiàn)的具體例子,一個操作系統(tǒng)只能有一個文件系統(tǒng)。
  • 在Spring中,每個Bean默認就是單例的,這樣做的優(yōu)點是Spring容器可以管理。
  • 在Servlet編程中,每個Servlet也是單例。
  • 在Spring MVC框架/struts1框架中,控制器對象也是單例。

單例模式的優(yōu)點

  • 由于單例模式只生成一個實例,減少了系統(tǒng)性能開銷,當一個對象的產(chǎn)生需要比較多的資源時,如讀取配置、產(chǎn)生其它依賴對象,則可以在應用啟動時直接產(chǎn)生一個單例對象,然后永久駐留內(nèi)存。
  • 單例模式可以在系統(tǒng)設置全局的訪問點,優(yōu)化和共享資源訪問,例如可以設計一個單例類,負責所有數(shù)據(jù)表的映射處理。

常見的五種單例模式實現(xiàn)方式

  1. 餓漢式(線程安全,調用效率高。但是,不能延時加載)
  2. 懶漢式(線程安全,調用效率不高。但是,可以延時加載)
  3. 雙重檢測鎖式(由于指令重排,在多線程環(huán)境下可能出現(xiàn)引用不空,但對象未初始化。不建議使用。)
  4. 靜態(tài)內(nèi)部類式(線程安全、調用效率高,可以延時加載)
  5. 枚舉單例(線程安全,調用效率高,不能延時加載)

如何選用?

  • 單例對象占用資源少,不需要延時加載:枚舉式 好于餓漢式
  • 單例對象占用資源大,需要延時加載:靜態(tài)內(nèi)部類式,好于懶漢式

代碼示例

餓漢式

package com.liu.singleton;

/**
 * 測試餓漢式單例模式
 *
 * @author Steve
 *
 */
public class SingletonDemo01 {
        //類初始化時,立即加載(沒有延時加載的優(yōu)勢)。加載類時是線程安全的。
        private static SingletonDemo01 instance = new SingletonDemo01();

        private SingletonDemo01() {
               //避免使用反射調用構造器時創(chuàng)建新的對象
               if (instance !=null) {
                      throw new RuntimeException("請使用getInstance方法獲取對象" );
               }
       }
        //方法沒有同步,調用效率高
        public static SingletonDemo01 getInstance() {
               return instance ;
       }
        // 反序列化時,如果定義了該方法,則直接返回此方法指定的對象,不會再單獨創(chuàng)建對象
        private Object readResolve() throws ObjectStreamException {
               return instance ;
       }
}

懶漢式

package com.liu.singleton;

/**
 * 測試懶漢式單例模式
 *
 * @author Steve
 *
 */
public class SingletonDemo02 {
        private static SingletonDemo02 instance ;

        private SingletonDemo02() {
          //避免使用反射調用構造器時創(chuàng)建新的對象
              if (instance !=null) {
                      throw new RuntimeException("請使用getInstance方法獲取對象" );
              }
       }

        public static synchronized SingletonDemo02 getInstance() {
               if (null == instance) {
                      instance = new SingletonDemo02 ();
              }
               return instance ;
       }
        // 反序列化時,如果定義了該方法,則直接返回此方法指定的對象,不會再單獨創(chuàng)建對象
        private Object readResolve() throws ObjectStreamException {
               return instance ;
       }
}

雙重檢查鎖式

package com.liu.singleton;

/**
 * 雙重檢查鎖實現(xiàn)單例模式
 *
 * @author Steve
 *
 */
public class SingletonDemo03 {
        private static SingletonDemo03 instance = null;

        private SingletonDemo03() {
          //避免使用反射調用構造器時創(chuàng)建新的對象
               if (instance !=null) {
                      throw new RuntimeException("請使用getInstance方法獲取對象" );
              }
       }

        public static SingletonDemo03 getInstance() {
               if (null == instance) {
                     SingletonDemo03 sc;
                      synchronized (SingletonDemo03.class) {
                            sc = instance;
                             if (sc == null ) {
                                   synchronized (SingletonDemo03.class) {
                                          if (sc == null ) {
                                                sc = new SingletonDemo03();
                                         }
                                  }
                                   instance = sc;
                            }
                     }
              }
               return instance ;
       }
        // 反序列化時,如果定義了該方法,則直接返回此方法指定的對象,不會再單獨創(chuàng)建對象
        private Object readResolve() throws ObjectStreamException {
               return instance ;
       }
}

靜態(tài)內(nèi)部類式

package com.liu.singleton;

/**
 * 靜態(tài)內(nèi)部類實現(xiàn)單例模式
 *
 * 外部類沒有static屬性,則不會像餓漢式那樣立即加載對象
 *
 * 只有真正調用getInstance(),才會加載靜態(tài)內(nèi)部類。加載類時是線程安全的,由JVM保證。
 *
 * instance是static final類型,保證了在內(nèi)存中只有這樣一個實例存在,而且只能被賦值一次,從而保證了線程安全性。
 *
 * 兼?zhèn)淞瞬l(fā)高效調用和延遲加載的優(yōu)勢
 *
 * @author Steve
 *
 */
public class SingletonDemo04 {
        private static class SingletonClassInstance {
               private static final SingletonDemo04 instance = new SingletonDemo04();
       }

        public static SingletonDemo04 getInstance() {
               return SingletonClassInstance.instance;
       }

        private SingletonDemo04() {
          //避免使用反射調用構造器時創(chuàng)建新的對象
               if (instance !=null) {
                      throw new RuntimeException("請使用getInstance方法獲取對象" );
              }
       }
        // 反序列化時,如果定義了該方法,則直接返回此方法指定的對象,不會再單獨創(chuàng)建對象
        private Object readResolve() throws ObjectStreamException {
               return SingletonClassInstance.instance ;
       }
}

枚舉式

package com.liu.singleton;

/**
 * 使用枚舉實現(xiàn)單例模式
 *
 * 優(yōu)點:實現(xiàn)簡單、枚舉本身是單例模式。由JVM從根本上提供保障,避免通過反射和反序列化創(chuàng)建對象的漏洞
 *
 * 缺點:無延遲加載
 *
 * @author Steve
 *
 */
public enum SingletonDemo05 {

        /**
        * 這個枚舉元素,本身就是單例的
        */
        INSTANCE;
        /**
        * 添加自己需要的操作
        */
        public void singletonOperation() {

       }
}

多線程測試各種實現(xiàn)方式的效率

package com.liu.singleton;

import java.util.concurrent.CountDownLatch;

/**
 * 使用CountDownLatch測試多線程環(huán)境下各個單例模式實現(xiàn)方式的效率
 *
 * @author  Steve
 *
 */
public class Client3 {
        public static void main(String[] args) throws InterruptedException {
               long start = System.currentTimeMillis();
               int threadNum = 10;
               final CountDownLatch countDownLatch = new CountDownLatch(threadNum);

               for (int i = 0; i < 10; i++) {
                      new Thread(new Runnable() {
                             @Override
                             public void run() {
                                   for (int i = 0; i < 1000000; i++) {
                                         Object o = SingletonDemo01.getInstance();
                                  }
                                  countDownLatch.countDown();
                            }

                     }).start();
              }
              countDownLatch.await(); // main線程阻塞,知道計數(shù)器值變?yōu)?,才會繼續(xù)往下執(zhí)行

               long end = System.currentTimeMillis();
              System. out.println("總耗時:" + (end - start));
       }
}

碼神手記——資深攻城獅的私房筆記。微信公眾平臺/知乎/頭條/簡書同步發(fā)文。感謝關注與轉發(fā)。

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容