Singleton-單例模式
單例顧名思義就是單個實例,保證在內存中只有一個實例。
使用場景用在只需要有一個實例存在的時候,不想讓別人在 new 一個實例出來的時候。
現在單例的寫法玩的越來越花哨了,各種寫法,本次介紹一下常用幾種方式及優缺點。
1、餓漢式
特點: JVM保證線程安全
優點:寫法比較簡單,推薦使用
缺點:不管是否用到,類裝載時就完成實例化了
public class Singleton01 {
/**
* 餓漢式
* 類加載到內存後,只實例化一個,JVM保證線程安全
*
* 這種寫法比較簡單,缺點就是不管是否用到,類裝載時就完成實例化了。
*/
private final static Singleton01 singleton = new Singleton01();
// 構造方法設置成private,別人也就不能 new 了
private Singleton01(){};
public static Singleton01 getInstance() {
return singleton;
}
// 可以定義自己的業務方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton01 s1 = Singleton01.getInstance();
Singleton01 s2 = Singleton01.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
2、懶漢式
特點:在用的時候初始化,不用不初始化
優點:即特點吧
缺點:非線程安全
注:爲什麼是非線程安全的?
如果一個線程調用 getInstance() 判斷 singleton 爲 null , 在還沒有執行到 singleton = new Singleton02(); 這一行的時候。
另外一個線程也開始調用 getInstance() 這時候在判斷 singleton 也是爲 null。
這樣就導致兩個線程分別執行了一次 singleton = new Singleton02();
所以說這個 singleton 就不再是同一個實例了。
public class Singleton02 {
/**
* 懶漢式
* 在用的時候在初始化,不用不初始化。
* 但是 線程不安全, 爲什麼是線程不安全的呢?
*
* 如果一個線程調用 getInstance() 判斷 singleton 爲 null , 在還沒有執行到 singleton =
new Singleton02(); 這一行的時候。
* 另外一個線程也開始調用 getInstance() 這時候在判斷 singleton 也是爲 null。
* 這樣就導致兩個線程分別執行了一次 singleton = new Singleton02();
* 所以說這個 singleton 就不再是同一個實例了。
*/
private static Singleton02 singleton = null;
private Singleton02(){}
public static Singleton02 getInstance() {
if(singleton == null) {
singleton = new Singleton02();
}
return singleton;
}
// 可以定義自己的業務方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton02 s1 = Singleton02.getInstance();
Singleton02 s2 = Singleton02.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
3、懶漢式-線程安全
特點:線程安全
優點:即特點
缺點:通過 synchronized 來解決線程安全問題, 但犧牲的是效率
public class Singleton03 {
/**
* 懶漢式-線程安全
* 可以通過 synchronized 來解決線程安全問題, 但犧牲的是效率。
*/
private static Singleton03 singleton = null;
private Singleton03(){}
public static synchronized Singleton03 getInstance() {
if(singleton == null) {
singleton = new Singleton03();
}
return singleton;
}
// 可以定義自己的業務方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton03 s1 = Singleton03.getInstance();
Singleton03 s2 = Singleton03.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
4、懶漢式 - 線程安全 - 雙重檢查
特點:線程安全
優點:在加鎖的同時,減小同步代碼塊的方式來提高效率
缺點:還是會犧牲效率
public class Singleton04 {
/**
* 懶漢式 - 線程安全 - 雙重檢查
*
* 在加鎖的同時,減小同步代碼塊的方式來提高效率
*/
private static Singleton04 singleton = null;
private Singleton04(){}
public static synchronized Singleton04 getInstance() {
if(singleton == null) {
synchronized(Singleton04.class) {
if(singleton == null) {
singleton = new Singleton04();
}
}
}
return singleton;
}
// 可以定義自己的業務方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton04 s1 = Singleton04.getInstance();
Singleton04 s2 = Singleton04.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
5、餓漢式-靜態內部類
特點:線程安全
優點:在加載外部類時不會加載內部類,這樣可以實現懶加載
缺點:這算是比較完美的寫法了,暫時沒有什麼缺點吧
public class Singleton05 {
/**
* 靜態內部類
*
* 在加載外部類時不會加載內部類,這樣可以實現懶加載
*
* 比較完美的寫法
*/
private Singleton05(){}
private static class Singleton05Holder{
private final static Singleton05 singleton = new Singleton05();
}
public static Singleton05 getInstance() {
return Singleton05Holder.singleton;
}
// 可以定義自己的業務方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton05 s1 = Singleton05.getInstance();
Singleton05 s2 = Singleton05.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
6、枚舉
特點:好像是Java創始人之一提出的,在 Effective Java 書中有提到
優點:解決了線程安全問題,還可以防止反序列化
注:枚舉單例爲什麼不會被反序列化?
枚舉類沒有構造方法
public enum Singleton06 {
/**
* 堪稱最完美的寫法 在 Effective Java 書中有提到
*
* 解決了線程安全問題,還可以防止反序列化
*
* 枚舉單例不會被反序列化的原因是:枚舉類沒有構造方法。
*/
singleton;
// 可以定義自己的業務方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton06 s1 = Singleton06.singleton;
Singleton06 s2 = Singleton06.singleton;
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
-------------------------------------------------------------------------- END -----------------------------------------------------------------------------
在工作中一般常用的還是第一種方式,簡單、安全,雖然有一點點小瑕疵,個人覺得還可以。
如有問題,歡迎交流!