設計模式實戰——開發中常用到的單例模式


本系列博客是自己在學習設計模式過程中收集整理的文章集合,其他文章參看設計模式傳送門

單例模式簡介

單例模式的目的是保證系統中只有類的一個實例對象,並且提供一個全局的入口點來獲取並使用這個實例對象。

使用單例模式可以防止用戶“胡亂”創建對象,耗費內存。而且有些對象從邏輯上來講一個系統中只應該存在一個,比如說Runtime類,使用單例模式也能很好的保證這一點。

本文介紹幾個我們平時開發過程中常用到的單例模式場景,來加深我們對單例模式的理解。

JDK中的單例模式

Runtime類封裝了Java運行時的環境。每一個java程序實際上都是啟動了一個JVM進程,那么每個JVM進程都是對應這一個Runtime實例,此實例是由JVM為其實例化的。每個 Java 應用程序都有一個 Runtime 類實例,使應用程序能夠與其運行的環境相連接。

由於Java是單進程的,所以,在一個JVM中,Runtime的實例應該只有一個。所以應該使用單例來實現。

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}
}

以上代碼為JDK中Runtime類的部分實現,可以看到,這其實是餓漢式單例模式。在該類第一次被classloader加載的時候,這個實例就被創建出來了。

Spring中的單例模式

我們知道在Spring中默認注入的Bean都是單例,那么Spring中的單例是怎么生成的呢?我們來看下Spring生成Bean的代碼。


@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
   Object singletonObject = this.singletonObjects.get(beanName);
   if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
      synchronized (this.singletonObjects) {
         singletonObject = this.earlySingletonObjects.get(beanName);
         if (singletonObject == null && allowEarlyReference) {
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
               singletonObject = singletonFactory.getObject();
               this.earlySingletonObjects.put(beanName, singletonObject);
               this.singletonFactories.remove(beanName);
            }
         }
      }
   }
   return singletonObject;
}

spring依賴注入時,使用了雙重判斷加鎖的單例模式,首先從緩存MAP中獲取bean實例,如果為null,對緩存map加鎖,然后再從緩存中獲取bean,如果繼續為null,就創建一個bean。

Spring並沒有使用私有構造方法來創建bean,而是通過singletonFactory.getObject()返回具體beanName對應的ObjectFactory來創建bean。實際上是調用了AbstractAutowireCapableBeanFactory的doCreateBean方法,返回了BeanWrapper包裝並創建的bean實例。

MyBatis中的單例模式

1. ErrorContext

ErrorContext是用在每個線程范圍內的單例,用於記錄該線程的執行環境錯誤信息。

public class ErrorContext {

  private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
  private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

  private ErrorContext stored;
  private String resource;
  private String activity;
  private String object;
  private String message;
  private String sql;
  private Throwable cause;

  private ErrorContext() {
  }

  public static ErrorContext instance() {
    ErrorContext context = LOCAL.get();
    if (context == null) {
      context = new ErrorContext();
      LOCAL.set(context);
    }
    return context;
  }

}

構造函數是private修飾,具有一個static的局部instance變量和一個獲取instance變量的方法,在獲取實例的方法中,先判斷是否為空如果是的話就先創建,然后返回構造好的對象。

只是這里有個有趣的地方是,LOCAL的靜態實例變量使用了ThreadLocal修飾,也就是說它屬於每個線程各自的數據,而在instance()方法中,先獲取本線程的該實例,如果沒有就創建該線程獨有的ErrorContext。

也就是說ErrorContext是線程范圍內的單例,而不是全局范圍內(JVM內)的單例。

2. VFS


public abstract class VFS {
  private static final Log log = LogFactory.getLog(VFS.class);

  /** The built-in implementations. */
  public static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, DefaultVFS.class };

  /** The list to which implementations are added by {@link #addImplClass(Class)}. */
  public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<Class<? extends VFS>>();

  /** Singleton instance. */
  private static VFS instance;

  /**
   * Get the singleton {@link VFS} instance. If no {@link VFS} implementation can be found for the
   * current environment, then this method returns null.
   */
  @SuppressWarnings("unchecked")
  public static VFS getInstance() {
    if (instance != null) {
      return instance;
    }
}

VFS是MyBatis中提供的文件系統類,存在感比較低。但是我們看下這個類的源代碼的話,的確是很標准的單例模式。

Log4j中的單例

Log4jLoggerFactory創建Logger時也是用的單例模式。代碼如下:


private static ConcurrentMap<String, Logger> log4jLoggers = new ConcurrentHashMap();

    Log4jLoggerFactory() {
    }

    public static Logger getLogger(String name) {
        Logger instance = (Logger)log4jLoggers.get(name);
        if (instance != null) {
            return instance;
        } else {
            Logger newInstance = new Logger(name);
            Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }

    public static Logger getLogger(String name, LoggerFactory loggerFactory) {
        Logger instance = (Logger)log4jLoggers.get(name);
        if (instance != null) {
            return instance;
        } else {
            Logger newInstance = loggerFactory.makeNewLoggerInstance(name);
            Logger oldInstance = (Logger)log4jLoggers.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }

PS:有一個問題,不同的多個Logger向同一個文件中打日志時,是怎么保證高效並且線程安全的???

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM