手寫七種單例模式


Java中單例模式定義:“一個類有且僅有一個實例,並且自行實例化向整個系統提供。”

單例模式應用的場景一般發現在以下條件下:

(1)資源共享的情況下,避免由於資源操作時導致的性能或損耗等。如上述中的日志文件,應用配置。

(2)控制資源的情況下,方便資源之間的互相通信。如線程池等。

第一種  餓漢模式

定義:在類加載的時候就立即初始化,並且創建單例對象。絕對線程安全,在線程還沒出現以前就是實例化了,不可能存在訪問安全問題。


優點:沒有加任何的鎖、執行效率比較高,在用戶體驗上來說,比懶漢式更好。


缺點:類加載的時候就初始化,不管用與不用都占着空間,浪費了內存。


Spring 中 IOC 容器 ApplicationContext 本身就是典型的餓漢式單例。

public class HungrySingleton {
    public static HungrySingleton hungrySingleton = null;
    static {
        hungrySingleton = new HungrySingleton();
    }
    private HungrySingleton() {
    }
    public HungrySingleton getInstance() {
        return hungrySingleton;
    }
}

第二種 懶漢式單例

懶漢式單例的特點是:被外部類調用的時候內部類才會加載。

public class LazySingleton {
    public static LazySingleton lazySingleton = null;
    private LazySingleton() {
    }
    //synchronized 解決多線程並發安全問題
    public synchronized static LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

第三種 雙重檢查鎖單例

線程安全的問題雖然解決了。但是,用synchronized 加鎖,在線程數量比較多情況下,如果 CPU 分配壓力上升,會導致大批量線程出現阻塞,從而導致程序運行性能大幅下降。

那么,有沒有一種更好的方式,既兼顧線程安全又提升程序性能呢?答案是肯定的。我們來看雙重檢查鎖的單例模式:

public class DoubleLockCheckSingleton {
    // volatile可以實現多線程的可見性 有序性 但是不保證一致性
    public volatile static DoubleLockCheckSingleton doubleLockCheckSingleton = null;
    private DoubleLockCheckSingleton() {}
    public static DoubleLockCheckSingleton getInstance() {
        // 最外成if 過濾掉不為null 的線程 提高效率
        if (doubleLockCheckSingleton == null) {
            synchronized (DoubleLockCheckSingleton.class) {
                if (doubleLockCheckSingleton == null) {
                    doubleLockCheckSingleton = new DoubleLockCheckSingleton();
                }
            }
        }
        return doubleLockCheckSingleton;
    }
}

第四種 內部類單例

雖然雙重檢查鎖確實優化了很多,但是,用到 synchronized 關鍵字,總歸是要上鎖,對程序性能還是存在一定影響的。難道就真的沒有更好的方案嗎?

當然是有的。我們可以從類初始化角度來考慮,

內部類一定是要在方法調用之前初始化,巧妙地避免了線程安全問題。 

/**
 * 內部類 線程安全 懶漢式
 * 完美的解決類懶漢時的內存浪費和synchronized的性能問題
 */
public class InnerClassSingleton {
    private InnerClassSingleton () {
        // 防止被反射破壞
        if (InnerClassHolder.innerClassSingleton != null) {
            throw new RuntimeException("不允許創建多實例");
        }
    }
    // static為了使用單利內存共性 final為了讓方法不被重寫 重載
    public static final InnerClassSingleton getInstance() {
        return InnerClassHolder.innerClassSingleton;
    }
    // 默認不加載 使用的時候才會加載
    private static class InnerClassHolder {
        private static final InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
    }
}

第五種 注冊式單例之枚舉式單例

注冊式單例又稱為登記式單例,就是將每一個實例都登記到某一個地方,使用唯一的標識獲取實例。

注冊式單例有兩種寫法:一種為容器緩存,一種為枚舉登記。先來看枚舉式單例的寫法,來看代碼:

public enum EnumSingleton {
    INSTANCE;
    public static EnumSingleton getInstance() {
        return INSTANCE;
    }
}

第六種 注冊式單例之容器緩存

容器式寫法適用於創建實例非常多的情況,便於管理。

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ContainerSingleton {
    private ContainerSingleton() {}
    public static Map<String, Object> ioc = new ConcurrentHashMap<>();
    public static Object getInstance(String className) {
        synchronized (ioc) {
            Object obj = null;
            if (!ioc.containsKey(className)) {
                try {
                    obj = Class.forName(className).newInstance();
                    ioc.put(className, obj);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return obj;
            } else {
                return ioc.get(className);
            }
        }
    }
}

第七種  ThreadLocal 線程單例 (注意是線程單例)

ThreadLocal 不能保證其創建的對象是全局唯一,但是能保證在單個線程中是唯一的,天生的線程安全。下面我們來看代碼:

那么 ThreadLocal 是如果實現單例呢?我們知道上面的單例模式為了達到線程安全的目的,給方法上鎖,以時間換空間。

ThreadLocal將所有的對象全部放在 ThreadLocalMap 中,為每個線程都提供一個對象,實際上是以空間換時間來實現線程間隔離的 。

/**
 * 每個線程內 獨立單例
 */
public class ThreadLocalSingleton {
    private ThreadLocalSingleton() {}
    public static final ThreadLocal<ThreadLocalSingleton> threadLocalSingleton =
            ThreadLocal.withInitial(() -> new ThreadLocalSingleton());
    public static ThreadLocalSingleton getInstance() {
        return threadLocalSingleton.get();
    }
}

單例模式總結

單例模式可以保證內存里只有一個實例,減少了內存開銷;可以避免對資源的多重占用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM