你真能說清楚單例模式嗎?
碼神手記——資深攻城獅的私房筆記。微信公眾平臺/知乎/頭條/簡書同步發(fā)文。感謝關注與轉發(fā)。
單例模式.PNG
核心作用
保證一個類只有一個實例,并且提供一個訪問該實例的全局訪問點。
常見應用場景
- Windows的Task Manager(任務管理器)是很典型的單例模式
- Windows的Recycle Bin(回收站)是單例應用。在系統(tǒng)運行中,回收站只維護僅有的一個實例。
- 項目中,讀取配置文件的類,一般也只有一個對象。沒有必要每次使用配置文件數(shù)據(jù)都new一個對象去讀取。
- 網(wǎng)站的計數(shù)器,一般也是采用單例模式實現(xiàn),否則難以同步。
- 應用程序的日志應用,一般都使用單例模式實現(xiàn),一般因為共享的日志文件一直處于打開狀態(tài),只能有一個實例去操作,否則內(nèi)容不好追加。
- 數(shù)據(jù)庫連接池的設計一般也采用單例模式。
- 操作系統(tǒng)的文件系統(tǒng),也是單例模式實現(xiàn)的具體例子,一個操作系統(tǒng)只能有一個文件系統(tǒng)。
- 在Spring中,每個Bean默認就是單例的,這樣做的優(yōu)點是Spring容器可以管理。
- 在Servlet編程中,每個Servlet也是單例。
- 在Spring MVC框架/struts1框架中,控制器對象也是單例。
單例模式的優(yōu)點
- 由于單例模式只生成一個實例,減少了系統(tǒng)性能開銷,當一個對象的產(chǎn)生需要比較多的資源時,如讀取配置、產(chǎn)生其它依賴對象,則可以在應用啟動時直接產(chǎn)生一個單例對象,然后永久駐留內(nèi)存。
- 單例模式可以在系統(tǒng)設置全局的訪問點,優(yōu)化和共享資源訪問,例如可以設計一個單例類,負責所有數(shù)據(jù)表的映射處理。
常見的五種單例模式實現(xiàn)方式
- 餓漢式(線程安全,調用效率高。但是,不能延時加載)
- 懶漢式(線程安全,調用效率不高。但是,可以延時加載)
- 雙重檢測鎖式(由于指令重排,在多線程環(huán)境下可能出現(xiàn)引用不空,但對象未初始化。不建議使用。)
- 靜態(tài)內(nèi)部類式(線程安全、調用效率高,可以延時加載)
- 枚舉單例(線程安全,調用效率高,不能延時加載)
如何選用?
- 單例對象占用資源少,不需要延時加載:枚舉式 好于餓漢式
- 單例對象占用資源大,需要延時加載:靜態(tài)內(nèi)部類式,好于懶漢式
代碼示例
餓漢式
package com.liu.singleton;
/**
* 測試餓漢式單例模式
*
* @author Steve
*
*/
public class SingletonDemo01 {
//類初始化時,立即加載(沒有延時加載的優(yōu)勢)。加載類時是線程安全的。
private static SingletonDemo01 instance = new SingletonDemo01();
private SingletonDemo01() {
//避免使用反射調用構造器時創(chuàng)建新的對象
if (instance !=null) {
throw new RuntimeException("請使用getInstance方法獲取對象" );
}
}
//方法沒有同步,調用效率高
public static SingletonDemo01 getInstance() {
return instance ;
}
// 反序列化時,如果定義了該方法,則直接返回此方法指定的對象,不會再單獨創(chuàng)建對象
private Object readResolve() throws ObjectStreamException {
return instance ;
}
}
懶漢式
package com.liu.singleton;
/**
* 測試懶漢式單例模式
*
* @author Steve
*
*/
public class SingletonDemo02 {
private static SingletonDemo02 instance ;
private SingletonDemo02() {
//避免使用反射調用構造器時創(chuàng)建新的對象
if (instance !=null) {
throw new RuntimeException("請使用getInstance方法獲取對象" );
}
}
public static synchronized SingletonDemo02 getInstance() {
if (null == instance) {
instance = new SingletonDemo02 ();
}
return instance ;
}
// 反序列化時,如果定義了該方法,則直接返回此方法指定的對象,不會再單獨創(chuàng)建對象
private Object readResolve() throws ObjectStreamException {
return instance ;
}
}
雙重檢查鎖式
package com.liu.singleton;
/**
* 雙重檢查鎖實現(xiàn)單例模式
*
* @author Steve
*
*/
public class SingletonDemo03 {
private static SingletonDemo03 instance = null;
private SingletonDemo03() {
//避免使用反射調用構造器時創(chuàng)建新的對象
if (instance !=null) {
throw new RuntimeException("請使用getInstance方法獲取對象" );
}
}
public static SingletonDemo03 getInstance() {
if (null == instance) {
SingletonDemo03 sc;
synchronized (SingletonDemo03.class) {
sc = instance;
if (sc == null ) {
synchronized (SingletonDemo03.class) {
if (sc == null ) {
sc = new SingletonDemo03();
}
}
instance = sc;
}
}
}
return instance ;
}
// 反序列化時,如果定義了該方法,則直接返回此方法指定的對象,不會再單獨創(chuàng)建對象
private Object readResolve() throws ObjectStreamException {
return instance ;
}
}
靜態(tài)內(nèi)部類式
package com.liu.singleton;
/**
* 靜態(tài)內(nèi)部類實現(xiàn)單例模式
*
* 外部類沒有static屬性,則不會像餓漢式那樣立即加載對象
*
* 只有真正調用getInstance(),才會加載靜態(tài)內(nèi)部類。加載類時是線程安全的,由JVM保證。
*
* instance是static final類型,保證了在內(nèi)存中只有這樣一個實例存在,而且只能被賦值一次,從而保證了線程安全性。
*
* 兼?zhèn)淞瞬l(fā)高效調用和延遲加載的優(yōu)勢
*
* @author Steve
*
*/
public class SingletonDemo04 {
private static class SingletonClassInstance {
private static final SingletonDemo04 instance = new SingletonDemo04();
}
public static SingletonDemo04 getInstance() {
return SingletonClassInstance.instance;
}
private SingletonDemo04() {
//避免使用反射調用構造器時創(chuàng)建新的對象
if (instance !=null) {
throw new RuntimeException("請使用getInstance方法獲取對象" );
}
}
// 反序列化時,如果定義了該方法,則直接返回此方法指定的對象,不會再單獨創(chuàng)建對象
private Object readResolve() throws ObjectStreamException {
return SingletonClassInstance.instance ;
}
}
枚舉式
package com.liu.singleton;
/**
* 使用枚舉實現(xiàn)單例模式
*
* 優(yōu)點:實現(xiàn)簡單、枚舉本身是單例模式。由JVM從根本上提供保障,避免通過反射和反序列化創(chuàng)建對象的漏洞
*
* 缺點:無延遲加載
*
* @author Steve
*
*/
public enum SingletonDemo05 {
/**
* 這個枚舉元素,本身就是單例的
*/
INSTANCE;
/**
* 添加自己需要的操作
*/
public void singletonOperation() {
}
}
多線程測試各種實現(xiàn)方式的效率
package com.liu.singleton;
import java.util.concurrent.CountDownLatch;
/**
* 使用CountDownLatch測試多線程環(huán)境下各個單例模式實現(xiàn)方式的效率
*
* @author Steve
*
*/
public class Client3 {
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
int threadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
Object o = SingletonDemo01.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await(); // main線程阻塞,知道計數(shù)器值變?yōu)?,才會繼續(xù)往下執(zhí)行
long end = System.currentTimeMillis();
System. out.println("總耗時:" + (end - start));
}
}
碼神手記——資深攻城獅的私房筆記。微信公眾平臺/知乎/頭條/簡書同步發(fā)文。感謝關注與轉發(fā)。