一、什么是單例模式:
所謂類的單例設計模式,就是采取一定的方法保證在整個的軟件系統中,對某個類只能存在一個對象實例,並且該類只提供一個取得其對象實例的方法(靜態方法)。
二、單例模式的實現方式:(7種)
1.餓漢式(靜態變量):
//餓漢式(靜態變量)
class Singleton {
//1. 構造器私有化, 外部能new
private Singleton() { } //2.本類內部創建對象實例 private final static Singleton instance = new Singleton(); //3. 提供一個公有的靜態方法,返回實例對象 public static Singleton getInstance() { return instance; } }
優:寫法比較簡單,就是在類裝載的時候就完成實例化。避免了線程同步問題。
缺:在類裝載的時候就完成實例化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費。
評價:這種模式可用,但可能會造成內存浪費
2.餓漢式(靜態代碼塊):
class Singleton {
//1. 構造器私有化, 外部能new
private Singleton() { } //2.本類內部創建對象實例 private static Singleton instance; static { // 在靜態代碼塊中,創建單例對象 instance = new Singleton(); } //3. 提供一個公有的靜態方法,返回實例對象 public static Singleton getInstance() { return instance; } }
與上面類似
3.懶漢式(線程不安全):
class Singleton {
private static Singleton instance; private Singleton() {} //提供一個靜態的公有方法,當使用到該方法時,才去創建 instance //即懶漢式 public static Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
優:起到了Lazy Loading的效果
缺:多線程環境下不可使用這種方式
評價:只能在單線程下使用
4.懶漢式(線程安全,同步方法)
// 懶漢式(線程安全,同步方法)
class Singleton {
private static Singleton instance; private Singleton() {} //提供一個靜態的公有方法,加入同步處理的代碼,解決線程安全問題 //即懶漢式 public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
優:解決了線程不安全問題
缺:效率太低了,每個線程在想獲得類的實例時候,執行getInstance()方法都要進行 同步
評價:不推薦使用
5.雙重檢查
class Singleton {
private static volatile Singleton instance; private Singleton() {} //提供一個靜態的公有方法,加入雙重檢查代碼,解決線程安全問題, 同時解決懶加載問題 //同時保證了效率, 推薦使用 public static synchronized Singleton getInstance() { if(instance == null) { synchronized (Singleton.class) { if(instance == null) { instance = new Singleton(); } } } return instance; } }
評價:線程安全;延遲加載;效率較高 ,推薦使用
6.靜態內部類:
// 靜態內部類完成, 推薦使用
class Singleton {
private static volatile Singleton instance; //構造器私有化 private Singleton() {} //寫一個靜態內部類,該類中有一個靜態屬性 Singleton private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } //提供一個靜態的公有方法,直接返回SingletonInstance.INSTANCE public static synchronized Singleton getInstance() { return SingletonInstance.INSTANCE; } }
評價:
- 靜態內部類方式在外部類被裝載時並不會立即實例化,而是在需要實例化 時,才會裝載內部類,從而完成內部的 實例化。
- 類的靜態屬性只會在第一次加載類的時候初始化,所以在這里,JVM幫助我們保證了線程的安全性,在類進行初始化時,別的線程是無法進入的。
- 避免了線程不安全,利用靜態內部類特點實現延遲加載,效率高,推薦使用
7.枚舉類
public class SingletonTest08 {
public static void main(String[] args) { Singleton instance = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE; System.out.println(instance == instance2); } } //使用枚舉,可以實現單例, 推薦 enum Singleton { INSTANCE; //屬性 public void sayOK() { System.out.println("ok~"); } }
評價:借助JDK1.5中添加的枚舉來實現單例模式。不僅能避免多線程同步問題,而 且還能防止反序列化重新創建新的對象,推薦使用
8.線程池
public class Singleton {
private Singleton() {} private static final ThreadLocal<Singleton> tlSingleton = new ThreadLocal<Singleton>() { @Override protected Singleton initialValue() { return new Singleton(); } }; public static Singleton getInstance() { return tlSingleton.get(); } }
9.CAS
public class Singleton {
/**利用AtomicReference */
private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); private Singleton(){} //用CAS確保線程安全 public static final Singleton getInstance(){ for (;;) { Singleton current = INSTANCE.get(); if (null != current) { return current; } current = new Singleton(); if (INSTANCE.compareAndSet(null, current)) { return current; } } } public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); System.out.println(singleton1 == singleton2); } }
三、單例模式的jdk使用:
package java.lang;
import java.io.*; import java.util.StringTokenizer; public class Runtime {//這里使用了餓漢式,因為runtime肯定會用到,不會浪費資源 private static Runtime currentRuntime = new Runtime(); public static Runtime getRuntime() { return currentRuntime; } /** Don't let anyone else instantiate this class */ private Runtime() {}
四、一些常見問題:https://blog.csdn.net/qq_40868987/article/details/103266261
1.靜態類和單例的區別:
單例可以繼承和被繼承,方法可以被override,而靜態方法不可以。
靜態方法中產生的對象會在執行后被釋放,進而被GC清理,不會一直存在於內存中。
靜態類會在第一次運行時初始化,單例模式可以有其他的選擇,即可以延遲加載。
靜態方法如果反復的初始化和釋放,則會占用很多資源,而使用單例模式將其常駐於內存可以更加節約資源。
靜態方法有更高的訪問效率。
單例模式很容易被測試。
2.序列化和反序列化會破壞單例嗎?
會,解決方案:
(1)https://blog.csdn.net/weixin_34087301/article/details/91400619
(2)https://www.cnblogs.com/chiclee/p/9097772.html
3.線程池的單例實現:
https://www.cnblogs.com/zhuangyan728/p/6706746.html
https://mp.weixin.qq.com/s?src=11×tamp=1586451635&ver=2268&signature=UhZ9OtKUNP-KVVt8s2q8t0MlH4P5Rts5d0hDebj9u0IWlovSgkG0bM8AQ-oLvRFXjkj8CdeTHSLLyedvrK5aMOfYd*U2mzXxINoDzykwmsI-jC1xJ5gNx*SLFWOJz0ys&new=1