設計模式實戰——開發中常用到的單例模式
本系列部落格是自己在學習設計模式過程中收集整理的文章集合,其他文章參看設計模式傳送門
單例模式簡介
單例模式的目的是保證系統中只有類的一個例項物件,並且提供一個全域性的入口點來獲取並使用這個例項物件。
使用單例模式可以防止使用者“胡亂”建立物件,耗費記憶體。而且有些物件從邏輯上來講一個系統中只應該存在一個,比如說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向同一個檔案中打日誌時,是怎麼保證高效並且執行緒安全的???
參考
- https://my.oschina.net/u/1024107/blog/1836488