本文重要關注點:html
單例模式屬於管理實例的創造型類型模式。單例模式保證在你的應用種最多隻有一個指定類的實例。java
讀取項目的配置信息的類能夠作成單例的,由於只須要讀取一次,且配置信息字段通常比較多節省資源。經過這個單例的類,能夠對應用程序中的類進行全局訪問。無需屢次對配置文件進行屢次讀取。設計模式
日誌器Logger在你的應用中是無處不在的。也應該只初始化一次,可是能夠處處使用。安全
若是你在使用一些數據分析工具例如Google Analytics。你就能夠注意到它們被設計成單例的,僅僅初始化一次,而後在用戶的每個行爲中均可以使用。多線程
將默認的構造器設置爲private。阻止其餘類從應用中直接初始化該類。併發
建立一個public static 的靜態方法。該方法用於返回一個單例類實例。oracle
還能夠選擇懶加載初始化更友好。ide
示例代碼參見如下類工具
public class Singleton {
private static Singleton instance;
// 構造器私有化
private Singleton(){
}
// 提供靜態方法
public static Singleton getInstance(){
// 懶加載初始化,在第一次使用時才建立實例
if(instance == null){
instance = new Singleton();
}
return instance;
}
public void display(){
System.out.println("Hurray! I am create as a Singleton!");
}
}
複製代碼
單元測試類:性能
package org.byron4j.cookbook.designpattern;
import org.byron4j.cookbook.designpattern.singleton.Singleton;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingletonTest {
@Test
public void test(){
final Set<Singleton> sets = new HashSet<>();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(){
Singleton s = Singleton.getInstance();
sets.add(s);
}
});
}
System.out.println(sets);
}
}
複製代碼
運行輸出以下,結果生成了多個Singleton實例:
[org.byron4j.cookbook.designpattern.singleton.Singleton@46b91344, org.byron4j.cookbook.designpattern.singleton.Singleton@1f397b96]
線程安全對於單例類來講是很是重要的。上述Singleton類是非線程安全的,由於在線程併發的場景下,可能會建立多個Singleton實例。
爲了規避這個問題,咱們能夠將 getInstance 方法用同步字 synchronized 修飾,這樣迫使線程等待直到前面一個線程執行完畢,如此就避免了同時存在多個線程訪問該方法的場景。
public static synchronized Singleton getInstance() {
// Lazy initialization, creating object on first use
if (instance == null) {
instance = new Singleton();
}
return instance;
}
複製代碼
這樣確實解決了線程安全的問題。可是,synchronized
關鍵字存在嚴重的性能問題。咱們還能夠進一步優化 getInstance 方法,將實例同步,將方法範圍縮小:
public static Singleton getInstance() {
// Lazy initialization, creating object on first use
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
複製代碼
單元測試三種方式耗時比較:
package org.byron4j.cookbook.designpattern;
import org.byron4j.cookbook.designpattern.singleton.Singleton;
import org.byron4j.cookbook.designpattern.singleton.SingletonSynchronized;
import org.byron4j.cookbook.designpattern.singleton.SingletonSynchronizedOptimized;
import org.junit.Test;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingletonTest {
@Test
public void test(){
final Set<Singleton> sets = new HashSet<>();
long startTime = System.currentTimeMillis();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(){
Singleton s = Singleton.getInstance();
sets.add(s);
}
});
}
System.out.println("test用時:" + (System.currentTimeMillis() - startTime));
System.out.println(sets);
}
@Test
public void testSynchronized(){
final Set<SingletonSynchronized> sets = new HashSet<>();
long startTime = System.currentTimeMillis();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(){
SingletonSynchronized s = SingletonSynchronized.getInstance();
sets.add(s);
}
});
}
System.out.println("testSynchronized用時:" + (System.currentTimeMillis() - startTime));
System.out.println(sets);
}
@Test
public void testOptimised(){
final Set<SingletonSynchronizedOptimized> sets = new HashSet<>();
long startTime = System.currentTimeMillis();
ExecutorService es = Executors.newFixedThreadPool(10000);
for(int i = 1; i <= 100000; i++){
es.execute(new Runnable() {
public void run(){
SingletonSynchronizedOptimized s = SingletonSynchronizedOptimized.getInstance();
sets.add(s);
}
});
}
System.out.println("testOptimised用時:" + (System.currentTimeMillis() - startTime));
System.out.println(sets);
}
}
複製代碼
運行測試用例,輸出以下:
test用時:1564
[org.byron4j.cookbook.designpattern.singleton.Singleton@68eae58e]
testSynchronized用時:3658
[org.byron4j.cookbook.designpattern.singleton.SingletonSynchronized@36429a46]
testOptimised用時:2254
[org.byron4j.cookbook.designpattern.singleton.SingletonSynchronizedOptimized@21571826]
複製代碼
能夠看到,最開始的實現方式性能是最好的,可是是非線程安全的; Synchronized 鎖住整個getInstance方法,能夠作到線程安全,可是性能是最差的; 縮小Synchronized範圍,能夠提升性能。
涉及單例類時還要注意clone方法的正確使用:
package org.byron4j.cookbook.designpattern.singleton;
/** * 單例模式實例 * 1. 構造器私有化 * 2. 提供靜態方法供外部獲取單例實例 * 3. 延遲初始化實例 */
public class SingletonZClone implements Cloneable{
private static SingletonZClone instance;
// 構造器私有化
private SingletonZClone(){
}
// 提供靜態方法
public static SingletonZClone getInstance(){
// 將同步鎖範圍縮小,下降性能損耗
if(instance == null){
synchronized (SingletonZClone.class){
if(instance == null){
instance = new SingletonZClone();
}
}
}
return instance;
}
/** * 克隆方法--改成public * @return * @throws CloneNotSupportedException */
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void display(){
System.out.println("Hurray! I am create as a SingletonZClone!");
}
}
複製代碼
默認狀況下clone時protected修飾的,這裏改成了public修飾,測試用例以下:
@Test
public void testClone() throws CloneNotSupportedException {
SingletonZClone singletonZClone1 = SingletonZClone.getInstance();
SingletonZClone singletonZClone2 = SingletonZClone.getInstance();
SingletonZClone singletonZClone3 = (SingletonZClone)SingletonZClone.getInstance().clone();
System.out.println(singletonZClone1 == singletonZClone2);
System.out.println(singletonZClone1 == singletonZClone3);
System.out.println(singletonZClone2 == singletonZClone3);
}
複製代碼
輸出以下:
true
false
false
咱們瞭解一下clone方法的API解釋, clone 後的對象雖然屬性值多是同樣的,可是已經不是同一個對象實例了:
x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)
clone方法返回一個被克隆對象的實例的副本,除了內存地址其餘屬性值都是同樣的,因此副本和被克隆對象不是同一個實例。 能夠看出clone方法破壞了單例類,爲防止該問題出現,咱們須要禁用clone方法,直接改成:
/** * 克隆方法--改成public * @return * @throws CloneNotSupportedException */
@Override
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
複製代碼
Java序列化機制容許將一個對象的狀態轉換爲字節流,就能夠很容易地存儲和轉移。 一旦對象被序列化,你就能夠對其進行反序列化--將字節流轉爲對象。 若是一個Singleton類被序列化,則可能建立重複的對象。 咱們可使用鉤子hook,來解釋這個問題。
在Java規範中有關於readResolve()方法的介紹:
對於可序列化的和外部化的類,readResolve() 方法容許一個類能夠替換/解析從流中讀取到的對象。 經過實現 readResolve 方法,一個類就能夠直接控制反序列化後的實例以及類型。 定義以下:
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; 複製代碼
readResolve 方法會在ObjectInputStream 從流中讀取一個對象時調用。ObjectInputStream 會檢測類是否認義了 readResolve 方法。 若是 readResolve 方法定義了,會調用該方法用於指定從流中反序列化後做爲返回的結果對象。 返回的類型要與原對象的類型一致,否則會出現 ClassCastException。
@Test
public void testSeria() throws Exception {
SingletonZCloneSerializable singletonZClone1 = SingletonZCloneSerializable.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
oos.writeObject(singletonZClone1);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
SingletonZCloneSerializable test = (SingletonZCloneSerializable) ois.readObject();
ois.close();
System.out.println(singletonZClone1 == test);
}
複製代碼
測試輸出: false; 說明反序列化的時候已經不是原來的實例了,如此會破壞單例模式。
因此咱們能夠覆蓋 readResolve 方法來解決序列化破壞單例的問題:
類 SingletonZCloneSerializableReadResolve 增長 readResolve 方法:
/** * 反序列化時返回instance實例,防止破壞單例模式 * @return */
protected Object readResolve(){
return getInstance();
}
複製代碼
執行測試用例:
@Test
public void testSReadResolve() throws Exception {
s = SingletonZCloneSerializableReadResolve.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.ser"));
oos.writeObject(s);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.ser"));
SingletonZCloneSerializableReadResolve test = (SingletonZCloneSerializableReadResolve) ois.readObject();
ois.close();
System.out.println(s == test);
}
複製代碼
輸出true,有效防止了反序列化對單例的破壞。
單例類是不多使用的,若是你要使用這個設計模式,你必須清楚的知道你在作什麼。由於全局範圍內僅僅建立一個實例,因此在資源受約束的平臺是存在風險的。
注意對象克隆。 單例模式須要仔細檢查並阻止clone方法。
多線程訪問下,須要注意線程安全問題。
當心多重類加載器,也許會破壞你的單例類。
若是單例類是可序列化的,須要實現嚴格類型