一、前言:
- 單例模式,是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。通過單例模式可以保證併發環境下中,應用該模式的一個類只有一個實例。即一個類只有一個對象實例。
- 常見的單例模式:
餓漢模式: 在程序啓動時即創建對象實例。
懶漢模式:僅當程序中使用到改對象時,纔回去創建對象。
二、單例模式實例:
1. 餓漢模式,程序啓動,對象實例被創建 【不推薦】:
/**
* @Des: 餓漢模式
*/
public class SingleTest01 {
// 靜態變量系統啓動就會被加載
private static SingleTest01 singleTest = new SingleTest01();
// 私有化構造方方
private SingleTest01(){
System.out.println(" init ");
}
// 返回對象
public static SingleTest01 newSingleTest(){
return singleTest;
}
}
優點:
- 代碼實現簡單,利用類加載機制保證線程安全。
缺點:
- 在程序啓動時,就已經完成實例化,如果對象沒有使用,會造成內存浪費。
- 如果對象在啓動時存在一些耗時操作,會影響到我們程序啓動時間
2、 懶漢模式, 當程序用到該實例時去創建對象,使用 synchronized 對獲取方法進行加鎖,實現併發安全【不推薦】:
/**
* @Des: 懶漢模式
*/
public class SingleTest02 {
// 靜態變量保存對象
private static SingleTest02 singleTest = null;
// 私有化構造方法
private SingleTest02(){
System.out.println(" init ");
}
// synchronized 修飾方法保證併發安全
public static synchronized SingleTest02 newSingleTest() {
if (singleTest == null){
singleTest = new SingleTest02();
}
return singleTest;
}
}
優點:
- 就是餓漢模式的缺點
缺點:
- 效率太低,加鎖的細粒度太大,其實僅僅在第一次創建對象時需要加鎖,實例化完成之後,獲取的時候完全沒有必要加鎖。
三、單例模式 – 懶漢模式優化:
第一種優化方案,縮小鎖粒度:
- 使用 volatile 保證線程可見性
- 對象爲被實例化時,通過代碼快進行加鎖,雙重檢驗保證最終結果單例。
/**
* @Des: 懶漢模式
*/
public class SingleTest03 {
// 靜態變量保存對象, volatile 保證每個線程讀取最新的數據
private static volatile SingleTest03 singleTest = null;
// 私有化構造方法
private SingleTest03(){
System.out.println(" init ");
}
// 獲取實例對象
public static SingleTest03 newSingleTest() {
if (singleTest == null){
// singleTest 爲空,表示沒有初始化,將當前類加鎖。
synchronized(SingleTest03.class){
// 雙重校驗,避免第一個if之後有多個線程在等待。
if (singleTest == null){
singleTest = new SingleTest03();
}
}
}
return singleTest;
}
}
第二種優化方案,使用靜態內部類:
- 利用類加載機制,保證實例化對象時僅有一個線程,內部類僅在使用時會初始化靜態屬性,實現了懶加載,效率高, 和第一中優化方案差不多,代碼如下:
/**
* @Des: 懶漢模式
*/
public class SingleTest04 {
private static class SingletonInstance{
private final static SingleTest04 SINGLETON = new SingleTest04();
}
private SingleTest04(){
System.out.println(" init ");
}
// 獲取實例對象
public static SingleTest04 newSingleTest() {
return SingletonInstance.SINGLETON;
}
}
四、目前推薦的單例的創建方式:
- 上面的方式雖然都實現了單例模式,各有各自的優缺點。但是他們都有一個公共的 缺點,無法防止暴力創建對象,例如: 反射、序列化、克隆。
- 在 《Effective Java》作者的Josh Bloch提倡我們使用枚舉的方式來創建單例對象,使用非常簡單。
關於枚舉的博文: https://blog.csdn.net/zhangyong01245/article/details/103322007 - 代碼示例:
/**
* @Des: 枚舉實現單例
*/
public enum Singleton {
// Singleton的單例對象,枚舉被加載時,由JVM創建,線程安全
INSTANCE;
// 單例對象中的方法
public void print(){
System.out.println(this.hashCode());
}
}
測試類:
public class Test {
public static void main(String[] args) {
for (int i =0; i<10;i++){
new Thread(new Runnable() {
public void run() {
Singleton singleton = Singleton.INSTANCE;
singleton.print();
}
}).start();
}
}
}
打印結果: