「設計模式專題」Singleton
單例模式,是一種常用的軟體設計模式。在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中,應用該模式的類一個類只有一個實例。即一個類只有一個對象實例。
餓漢式單例模式
優點
- 線程安全
- 在類載入的同事已經創建好一個靜態對象,調用時反應速度快
缺點
- 資源效率不高,可能 getInstance() 永遠不會執行到
- 執行該類的其他靜態方法或者載入了該類 (class.forName),那麼這個實例仍然能初始化
public class HungrySingleton {
private final static HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
public static void main(String[] args) {
/** 線程安全 */
int count = 1000;
CountDownLatch countDownLatch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(() -> {
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(System.currentTimeMillis() + ":" + instance);
countDownLatch.countDown();
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
/** 通過反射 那麼這個實例仍然能初始化 */
// try {
// Class<?> clazz = Class.forName("hungry.HungrySingleton");
// Object instance1 = clazz.newInstance();
// Object instance2 = clazz.newInstance();
//
// System.out.println(instance1);
// System.out.println(instance2);
// } catch (ClassNotFoundException e) {
// e.printStackTrace();
// } catch (IllegalAccessException e) {
// e.printStackTrace();
// } catch (InstantiationException e) {
// e.printStackTrace();
// }
}
}
懶漢式單例模式
Lazy_v1_SimpleSingleton
優點
- 避免了餓漢式的那種在沒有用到的情況下創建實例,資源利用率高
缺點
- 線程不安全
- 執行該類的其他靜態方法或者載入了該類 (class.forName),那麼這個實例仍然初始化
public class Lazy_v1_SimpleSingleton {
private static Lazy_v1_SimpleSingleton instance;
private Lazy_v1_SimpleSingleton() {
}
public static Lazy_v1_SimpleSingleton getInstance() {
if (instance == null) {
instance = new Lazy_v1_SimpleSingleton();
}
return instance;
}
}
Lazy_v2_SynchronizedSingleton
優點
- 避免了餓漢式的那種在沒有用到的情況下創建實例,資源利用率高
- 線程安全
缺點
- 第一次載入時不夠快,多線程使用不必要的同步開銷大
- 執行該類的其他靜態方法或者載入了該類(class.forName),那麼這個實例仍然初始化
public class Lazy_v2_SynchronizedSingleton {
private static Lazy_v2_SynchronizedSingleton instance;
private Lazy_v2_SynchronizedSingleton() {
}
public synchronized static Lazy_v2_SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new Lazy_v2_SynchronizedSingleton();
}
return instance;
}
public static void main(String[] args) {
try {
Class<?> clazz = Class.forName("lazy.Lazy_v2_SynchronizedSingleton");
Object instance1 = clazz.newInstance();
Object instance2 = clazz.newInstance();
System.out.println(instance1);
System.out.println(instance2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
Lazy_v3_DoubleCheckSingleton
優點
- 避免了餓漢式的那種在沒有用到的情況下創建實例,資源利用率高
- 線程安全
缺點
- 執行該類的其他靜態方法或者載入了該類(class.forName),那麼這個實例仍然初始化
- 由於java內存模型一些原因偶爾失敗
- 非原子操作
- 給 instance 分配內存
- 調用 instance 的構造函數來初始化成員變數,形成實例
- 將 instance 對象指向分配的內存空間(執行完這步 singleton才是非 null 了) --> 123 or 132
- 結論:如果是後者,則在 3 執行完畢、2 未執行之前,被線程二搶佔了,這時 instance 已經是非 null 了(但卻沒有初始化),所以線程二會直接返回 instance,然後使用,然後順理成章地報錯;由於有一個『instance已經不為null但是仍沒有完成初始化』的中間狀態,而這個時候,如果有其他線程剛好運行到第一層 if (instance == null) 這裡,這裡讀取到的 instance 已經不為 null 了,所以就直接把這個中間狀態的instance拿去用了,就會產生問題。
- 解決辦法
- Lazy_v4_VolatileDoubleCheckSingleton 不讓指令重排序 -lazy.Lazy_v5_InnerClassSingleton 可以指令重排序,但是不讓外部看見
public class Lazy_v3_DoubleCheckSingleton {
private static Lazy_v3_DoubleCheckSingleton instance;
private Lazy_v3_DoubleCheckSingleton() {
}
public static Lazy_v3_DoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (Lazy_v3_DoubleCheckSingleton.class) {
if (instance == null) {
instance = new Lazy_v3_DoubleCheckSingleton();
}
}
}
return instance;
}
public static void main(String[] args) {
long count = 1000000;
synchronizedSingleton(count);
noSynchronizedSingleton(count);
}
private static void synchronizedSingleton(long count) {
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
Lazy_v3_DoubleCheckSingleton synchronizedSingleton = Lazy_v3_DoubleCheckSingleton.getInstance();
}
long end = System.currentTimeMillis();
System.out.println("synchronizedSingleton 耗時:" + (end - start));
}
private static void noSynchronizedSingleton(long count) {
long start = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
}
long end = System.currentTimeMillis();
System.out.println("noSynchronizedSingleton 耗時:" + (end - start));
}
}
Lazy_v4_VolatileDoubleCheckSingleton
優點
- 避免了餓漢式的那種在沒有用到的情況下創建實例,資源利用率高
- 線程安全
缺點
- 執行該類的其他靜態方法或者載入了該類(class.forName),那麼這個實例仍然初始化
- 禁止指令重排,導致性能下降
public class Lazy_v4_VolatileDoubleCheckSingleton {
/** volatile關鍵字的一個作用是禁止指令重排,把instance聲明為volatile之後,對它的寫操作就會有一個內存屏障 */
private static volatile Lazy_v4_VolatileDoubleCheckSingleton instance;
private Lazy_v4_VolatileDoubleCheckSingleton() {
}
public static Lazy_v4_VolatileDoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (Lazy_v4_VolatileDoubleCheckSingleton.class) {
if (instance == null) {
instance = new Lazy_v4_VolatileDoubleCheckSingleton();
}
}
}
return instance;
}
}
Lazy_v5_InnerClassSingleton
優點
- 避免了餓漢式的那種在沒有用到的情況下創建實例,資源利用率高
- 線程安全
缺點
- 執行該類的其他靜態方法或者載入了該類(class.forName),那麼這個實例仍然初始化
- 禁止指令重排,導致性能下降
public class Lazy_v5_InnerClassSingleton {
/**
* static 是為了使單例的空間共享
* final 保證這個方法不會被重寫,重載
*/
public static final Lazy_v5_InnerClassSingleton getInstance() {
return SingletonHolder.instance;
}
/**
* 這種寫法非常巧妙:
* 對於內部類 SingletonHolder,它是一個餓漢式的單例實現,在SingletonHolder初始化的時候會
* 由ClassLoader來保證同步,使 instance 是一個真·單例。
* 同時,由於SingletonHolder是一個內部類,只在外部類的Singleton的getInstance()中被使用,
* 所以它被載入的時機也就是在getInstance()方法第一次被調用的時候。
* <p>
* 從內部看是一個餓漢式的單例,但是從外部看來,又的確是懶漢式的實現。
*/
private static class SingletonHolder {
private static final Lazy_v5_InnerClassSingleton instance = new Lazy_v5_InnerClassSingleton();
}
/** 防止 反射 攻擊! */
private static boolean initialized = false;
private Lazy_v5_InnerClassSingleton() {
synchronized (Lazy_v5_InnerClassSingleton.class){
if(initialized == false){
initialized = !initialized;
}
else{
throw new RuntimeException("單例已被侵犯");
}
}
}
public static void main(String[] args) {
try {
Class<?> clazz = Lazy_v5_InnerClassSingleton.class;
Constructor c = clazz.getDeclaredConstructor(null);
c.setAccessible(true);
Object o = c.newInstance();
System.out.println(o);
Object o1 = c.newInstance();
System.out.println(o1);
Object o2 = c.newInstance();
System.out.println(o2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
防止序列反序列話單例模式
public class SerializableSingleton implements Serializable {
private static final long serialVersionUID = -1994001829338263901L;
private SerializableSingleton() {
}
public final static SerializableSingleton INSTANCE = new SerializableSingleton();
public static SerializableSingleton getInstance(){
return INSTANCE;
}
/**
* @method_name: readResolve
* @describe: 防止 序列化和反序列化後 破壞單例模式規則 啟用 readResolve() 方法
**/
public Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
public static void main(String[] args) {
SerializableSingleton s1;
SerializableSingleton s2 = SerializableSingleton.getInstance();
FileInputStream fis;
ObjectInputStream ois;
FileOutputStream fos = null;
try {
fos = new FileOutputStream("serialize.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
fis = new FileInputStream("serialize.obj");
ois = new ObjectInputStream(fis);
s1 = (SerializableSingleton) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
註冊式單例模式
RegisterSingleton
優點
- 登記式單例模式可以被擠成(餓漢和懶漢模式構造方法都是稀有的,因而不能被繼承的)
- 登記式單例實際上維護的是一組單例類的實例,將這些實例存儲到一個Map(登記簿)中,對於已經登記過的單例,則從工廠直接返回,對於沒有登記的,則先登記,而後返回
public class RegisterSingletonChildA extends RegisterSingleton {
public static RegisterSingletonChildA getInstance() {
return (RegisterSingletonChildA) RegisterSingletonChildA
.getInstance("org.crayzer.demo.singleton.register.RegisterSingletonChildA");
}
//隨便寫一個測試的方法
public String about() {
return "---->我是RegisterSingleton的一個子類RegisterSingletonChildA";
}
}
public class RegisterSingletonChildB extends RegisterSingleton {
public static RegisterSingletonChildB getInstance() {
return (RegisterSingletonChildB) RegisterSingletonChildB
.getInstance("org.crayzer.demo.singleton.register.RegisterSingletonChildB");
}
//隨便寫一個測試的方法
public String about() {
return "---->我是RegisterSingleton的一個子類RegisterSingletonChildB";
}
}
public class RegisterSingleton {
private static Map<String, RegisterSingleton> registerSingletonMap = new ConcurrentHashMap<>();
public static Map<String, RegisterSingleton> getRegisterSingletonMap() {
return registerSingletonMap;
}
protected RegisterSingleton() {
System.out.println("RegisterSingleton 的構造函數被調用,創建實例中...");
}
public static synchronized RegisterSingleton getInstance(String name) {
if (name == null) {
name = RegisterSingleton.class.getName();
System.out.println("name不存在,name = " + name);
}
if (registerSingletonMap.get(name) == null) {
try {
System.out.println("-->name對應的值不存在,開始創建");
registerSingletonMap.put(name, (RegisterSingleton) Class.forName(name).newInstance());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}else {
System.out.println("-->name對應的值存在");
}
System.out.println("-->返回name對應的值");
return registerSingletonMap.get(name);
}
/**
* 測試 多線程環境
* @param args
*/
public static void main(String[] args) {
// testMultithreaded();
// System.out.println("=============華麗的分割線=============");
testExtends();
}
private static void testExtends() {
System.out.println("-----------------登記式單例模式----------------");
System.out.println("第一次取得實例(登記式)");
RegisterSingleton s1 = RegisterSingleton.getInstance(null);
System.out.println(s1);
System.out.println("第二次取得實例(登記式)");
RegisterSingletonChildA s3 = RegisterSingletonChildA.getInstance();
System.out.println(s3);
System.out.println(s3.about());
System.out.println("第三次取得實例(登記式)");
RegisterSingletonChildB s4 = RegisterSingletonChildB.getInstance();
System.out.println(s4);
System.out.println(s4.about());
System.out.println("第四次取得實例(登記式)");
RegisterSingletonChildB s5 = RegisterSingletonChildB.getInstance();
System.out.println(s5);
System.out.println(s5.about());
System.out.println("第五次取得實例(非正常直接new子類的構造方法)");
RegisterSingletonChildB s6 = new RegisterSingletonChildB();
System.out.println(s6);
System.out.println(s6.about());
System.out.println("輸出父類中Map保存的所有單例,可以看出,直接new出來的實例並沒有存在Map中");
System.out.println(s1.getRegisterSingletonMap());
System.out.println();
}
public static void testMultithreaded() {
int count = 100;
CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
new Thread(() -> {
RegisterSingleton singleton = RegisterSingleton.getInstance("org.crayzer.demo.singleton.register.RegisterSingleton");
System.out.println(System.currentTimeMillis() + ":" + singleton);
latch.countDown();
}).start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
RegisterSingleton singleton = new RegisterSingleton();
System.out.println(singleton.getRegisterSingletonMap());
RegisterSingletonChildA singleton1 = new RegisterSingletonChildA();
System.out.println(singleton1.getRegisterSingletonMap());
RegisterSingletonChildB singleton2 = new RegisterSingletonChildB();
System.out.println(singleton2.getRegisterSingletonMap());
}
}
Enum
public enum EnumSingleton {
RED(){
private int r = 255;
private int g = 0;
private int b = 0;
},BLACK(){
private int r = 0;
private int g = 0;
private int b = 0;
},WHITE(){
private int r = 255;
private int g = 255;
private int b = 255;
};
public void func1() {
}
public static void main(String[] args) {
System.out.println(EnumSingleton.RED);
}
}
※IE瀏覽器的緩存問題
※SQL Server服務遠程過程調用失敗解決
TAG:程序員小新人學習 |