設計模式系列之單例模式

本文循序漸進介紹單例模式的幾種實現方式,以及Jdk中使用到單例模式的例子,以及sring框架中使用到的單例模式例子。

餓漢式

package signgleton;
/**
* 單例模式簡單的實現
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}

”餓漢式“只是形象的比喻,因為他想要這個實例的時候,不需要等待,別廢話,給哥拿來。通過static的初始化方式,藉助類第一次被加載時,就把Singleton實例給創建出來了,並存儲在JVM的方法區,屬於類變量,被所有的實例共享。不同的線程調用都返回一個實例,所以這樣也保證了線程安全。

它還有個孿生兄弟,靜態代碼塊來實例化:

package signgleton;
/**
* 通過靜態代碼塊創建實例對象
*/
public class StaticSignleton {
private static StaticSignleton instance;
/**
* 靜態代碼塊創建實例
*/
static {

instance = new StaticSignleton();
}

private StaticSignleton() {
}
public static StaticSignleton getInstance() {
return instance;
}
}

科普一下類初始化順序:

  • 靜態變量、靜態代碼塊初始化
  • 構造函數
  • 自定義構造函數

餓漢式缺點:因為在類被加載的時候對象就會被實例化,這可能會造成不必要的消耗,如果你的程序不在乎這點消耗那就當我沒說。

下面介紹兩種方式解決上面的問題:第一是使用靜態內部類,第二是使用懶漢式

靜態內部類

package signgleton;
/**
* 使用靜態內部類獲取單例實例
*/
public class StaticInnerClassSingleton {

private static class InnerSingletonClass{

private static final StaticInnerClassSingleton innerInstance = new StaticInnerClassSingleton();

}
private StaticInnerClassSingleton() {

}
public static final StaticInnerClassSingleton getStaticInstance() {
return InnerSingletonClass.innerInstance;
}
}

靜態內部類同樣藉助了JVM這個大佬來保證線程安全,只是他在類加載的時候並沒有立即實例化對象,而是採用了延遲加載策略,只有調用getStaticInstance的時候才用內部類去創建實例

線程不安全的懶漢式

package signgleton;
/**
* 線程不安全的懶漢式
*/
public class UnsafeSingleton {

private static UnsafeSingleton unsafeSingleton;
private UnsafeSingleton() {

}
public static UnsafeSingleton getUnsafeSingleton() {
if (unsafeSingleton == null) {
unsafeSingleton = new UnsafeSingleton();
}
return unsafeSingleton;
}

}

雖然這樣寫達到了使用的時候才實例化的目的,但是也帶來的線程安全問題。在多線程下,可能有兩個以上的線程同時進入if(unsafeInstance == null),這樣會發生一些奇怪不定的結果。

線程安全的懶漢式

package signgleton;
/**
* 線程安全的懶漢式
*/
public class SafeSingleton {
private static SafeSingleton safeSingleton;
private SafeSingleton() {
}
public static synchronized SafeSingleton getSafeSingleton() {
if (safeSingleton == null) {
safeSingleton = new SafeSingleton();
}
return safeSingleton;
}
}

這種方式在方法上加synchronized同步關鍵字解決了餓漢式線程安全問題,但是因為每次調用都加鎖,極大地降低了性能,因為只有第一次創建實例時需要加鎖,弄成現在每次都加鎖。有沒有解決辦法呢,當然有,前輩們都是很聰明的,想出了雙重校驗鎖這個經典的例子.

雙重校驗鎖

package signgleton;
/**
* 線程不安全雙重校驗鎖
*/
public class UnSafeTwoCheckSingleton {
private static UnSafeTwoCheckSingleton singleton;
private UnSafeTwoCheckSingleton() {

}
public static UnSafeTwoCheckSingleton getSingleton() {
if (singleton == null) {
synchronized (UnSafeTwoCheckSingleton.class) {
if (singleton == null) {
singleton = new UnSafeTwoCheckSingleton();
}
}
}
return singleton;
}
}

雙重校驗鎖的形式主要是縮小了鎖的範圍,但是熟悉多線程編程的同學就可以看得出來,即使這樣做還是有線程安全問題,這裡存在一個多個線程共享變量的可見性問題(這部分我不太懂原理),解決方案就是使用volatile

使用volatile優化

package signgleton;
/**
* 線程安全雙重校驗鎖
*/
public class SafeTwoCheckSingleton {
private static volatile SafeTwoCheckSingleton singleton;
private SafeTwoCheckSingleton() {

}
public static SafeTwoCheckSingleton getSingleton() {
if (singleton == null) {
synchronized (SafeTwoCheckSingleton.class) {
if (singleton == null) {
singleton = new SafeTwoCheckSingleton();
}
}
}
return singleton;
}
}

你以為這樣就安全了嗎,就想下班了嗎?還沒完,序列化這個惡棍會破壞單例,防範序列化這個惡棍破壞單例,可以在類中定義我們獲取實例的策略,既加readResolve。

防範序列化破壞單例

package signgleton;
import java.io.Serializable;
/**
* 線程安全雙重校驗鎖
*/
public class SafeTwoCheckSingleton implements Serializable{
private static volatile SafeTwoCheckSingleton singleton;
private SafeTwoCheckSingleton() {
}
public static SafeTwoCheckSingleton getSingleton() {
if (singleton == null) {
synchronized (SafeTwoCheckSingleton.class) {
if (singleton == null) {
singleton = new SafeTwoCheckSingleton();
}
}
}
return singleton;
}

private Object readResolve() {
return singleton;
}
}

單例模式案例

這麼多的實現方式,你會問,有什麼用?用處可大了,下面講兩個使用實例,一個jdk的Runtime, 一個是Spring框架中的單例模式。

Runtime:是一個封裝了JVM進程的類,每一個JAVA程序實際上都是JVM的一個進程,每一個進程都是對應這麼一個Runtime實例。

源碼如下:

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

這裡使用了餓漢式單例模式。

下面我們來看看看spring 中的單例模式,spring中使用的是單例註冊表的特殊方式實現的單例模式,所以說模式是死的,需要靈活得運用。

看看單例註冊表的實現原理demo:

package signgleton;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 單例註冊表demo
*/
public class SingletonRegTest {
private final static Map<string> singletonObjects = new ConcurrentHashMap<string>();
/**
* 類加載時初始化一個實例
*/
static {
SingletonRegTest singletonRegTest = new SingletonRegTest();
singletonObjects.put(singletonRegTest.getClass().getName(), singletonRegTest);
}
public static SingletonRegTest getInstance(String name) {
if (name == null) {

// 默認分配一個實例
name = "signgleton.SingletonRegTest";
}
if (singletonObjects.get(name) == null) {
try {
// 將默認實例放入緩存中
singletonObjects.put(name, Class.forName(name).newInstance());
} catch (Exception ex) {
ex.printStackTrace();
}
}
return (SingletonRegTest) singletonObjects.get(name);
}
}
/<string>/<string>

再來看看spring 源碼:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
@SuppressWarnings("unchecked")
protected T doGetBean(
final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
final String beanName = transformedBeanName(name);
Object bean;
// 從單例註冊表中檢查是否存在單例緩存
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
...
// 返回緩存實例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
else {
...
try {
...
// 如果是單例模式
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);

}
catch (BeansException ex) {
...
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// 如果是原型模式
else if (mbd.isPrototype()) {
...
}
// 其他模式
else {
...
}
}
catch (BeansException ex) {
...
}
}
return (T) bean;
}
}

/<object>

我們進入 getSingleton()方法:


import java.util.Map;
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
// 通過 ConcurrentHashMap 實現單例註冊表
private final Map<string> singletonObjects = new ConcurrentHashMap<string>(64);
public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
// 檢查緩存中是否存在實例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
...
try {
singletonObject = singletonFactory.getObject();
}
catch (BeanCreationException ex) {

...
}
finally {
...
}
// 如果實例對象在不存在,我們註冊到單例註冊表中。
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
}
}
}
/<string>/<string>

是不是和我們的單例註冊表demo很相似。

單例模式的講解後面隨著學習到其他框架再做相應的補充,也歡迎大家獻言獻策。


分享到:


相關文章: