什么是單例模式?
在我們正式講解單例模式之前,沒有了解過的小伙伴可能會有疑問...到底啥叫單例模式??其實單例模式呢,是我們設計模式中的一種,所謂的設計模式,你可以把它理解為一個模板,也就是你在實現某種業務的時候,選擇適配的設計模式,根據這個模板來改你對應的業務代碼
Java設計模式是解決特定軟件設計問題的經典、可復用的方案模板,分為創建型、結構型和行為型三大類,幫助開發者編寫更靈活、可維護的代碼。
那么我們的單例模式呢,指的是實例對象只會被創建一次這樣的設計模式~~
為了實現這樣的要求,單例模式中又有兩種形式:餓漢模式和懶漢模式,接下來我們將會為大家一一介紹這兩種模式
餓漢模式
什么是餓漢模式
所謂的餓漢模式,其實指的是實例從代碼剛開始運行的時候就已經創建好的模式,那實例就處在一個等著被調用的狀態,所以就一直餓著來等待資源~~因此就叫做餓漢模式啦
餓漢模式實現
class Singleton { //餓漢模式private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton() {}}
public class Demo19 {public static void main(String[] args) {Singleton t1 = Singleton.getInstance();Singleton t2 = Singleton.getInstance();boolean res = t1 == t2;System.out.println(res);}
}
如上,我們運行結果為true,因為此時它們引用的都是同一個實例~~所以是符合單例模式的
我們可以發現,單例模式的實現,即外部無法創建一個實例是通過將構造方法變為private來實現的,此時的話外部只能通過getInstance()方法來訪問內部已經創建好的那個實例
線程安全問題
那么,餓漢模式有沒有線程安全問題呢?我們可以發現,餓漢模式中只有讀操作,所以是沒有線程安全問題的~~可以放心大膽使用
不過餓漢模式還是有缺點的,因為實例一開始就被創建了,一直等著被使用,這是很浪費資源的行為~~
懶漢模式
什么是懶漢模式
所謂的懶漢模式,指的是實例只有在被使用的時候才會開始創建,因此聽起來就很懶啦,所以就被叫做懶漢模式,不過這里的"懶"可是褒義詞,因為這可以節約資源~~
懶漢模式實現
class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) { //判斷是否有實例創建了instance = new SingletonLazy();}return instance;}private SingletonLazy() {}
}
public class Demo20 { //懶漢模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}
上述我們給出了一個按照描述的懶漢模式,實例一開始是null,只有當被調用的時候才會根據判斷實例是否創建過來創建實例
線程安全問題
當我們細看上述代碼的時候,其實可以發現,對于instance對象,我們既有讀操作,又有寫操作,所以事實上,這個代碼是存在線程安全問題的
當t1和t2同時調用的時候,有可能會創建兩個實例,這就不符合單例模式的要求了;
我們細看可以發現,此時創建實例這個操作并不是原子的,是if + 創建一起的,這也是會引發線程安全問題的原因
因此,為了解決上述問題,我們可以把這個操作加個鎖,把它們變成原子的~~
解決原子性問題代碼
class SingletonLazy {private static SingletonLazy instance = null;private static Object locker = new Object();public static SingletonLazy getInstance() {synchronized (locker) { //使得if和創建實例變成一個整體的原子操作,防止多個線程同時操作時創建多個實例if (instance == null) { //判斷是否有實例創建了instance = new SingletonLazy();}}return instance;}private SingletonLazy() {}
}
public class Demo20 { //懶漢模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}
OK~~我們的原子性問題已經解決啦(●'?'●)
但是...我們再來看下這個代碼,線程安全問題有沒有解決完呢?回顧我們P4中提到的引起線程安全問題的原因,此時其實我們是兩個線程在對一個對象進行操作,所以是很可能發生指令重排序和內存可見性問題的,因此我們應該給這個對象加上volatile~~
解決指令重排序和內存可見性問題
class SingletonLazy {private static volatile SingletonLazy instance = null; //volatile防止指令重排序private static Object locker = new Object();public static SingletonLazy getInstance() {synchronized (locker) { //使得if和創建實例變成一個整體的原子操作,防止多個線程同時操作時創建多個實例if (instance == null) { //判斷是否有實例創建了instance = new SingletonLazy();}}return instance;}private SingletonLazy() {}
}
public class Demo20 { //懶漢模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}
OK~~~這下這兩個問題也解決啦(●'?'●)
這樣看起來,線程好像是沒啥安全問題了勒,那有沒有啥可以優化的?
經典生活例子
為了使大家更好理解我們的優化策略,在po出代碼之前,我們先搞個生活化例子理解一下~~
比如說學校里面的校花突然恢復單身了,那廣大單身男青年們聽到這個消息之后都很開心吶,不過大家素質都很高,于是在追求校花的時候排起了隊,按序追求,你很幸運的搶到了第一個,你開始追求校花之后就相當于追求校花這個操作加鎖了,別人就不能進行了,校花覺得你特別好,于是答應和你在一起啦~~[成功創建了實例],但是隊伍里面的人此時還不知道,第二個人此時又去追求校花,追求校花這個操作就又加鎖了,這個時候她就說我有男朋友啦,第二個人出去是不是就會告訴其它人,校花有男朋友了這件事情,那么實際上,他們就不會再進行追求校花這個操作了,即甚至連競爭鎖這個操作都不會去做,因為這是浪費資源的
所以與例子同樣的問題,我們這個代碼此時每個線程都還會再去競爭一次鎖,但如果實例已經存在了,就沒有競爭鎖的必要了,只有一開始的幾個線程在實例被創建好之前就進入創建實例過程會競爭一下鎖~~~
解決重復競爭鎖問題
class SingletonLazy {private static volatile SingletonLazy instance = null; //volatile防止指令重排序private static Object locker = new Object();public static SingletonLazy getInstance() {if (instance == null) { //后續線程可以防止重復加鎖synchronized (locker) { //使得if和創建實例變成一個整體的原子操作,防止多個線程同時操作時創建多個實例if (instance == null) { //判斷是否有實例創建了instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}
public class Demo20 { //懶漢模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}
標準懶漢模式實現代碼
我們這里總結一下解決完所有問題之后的代碼
class SingletonLazy {private static volatile SingletonLazy instance = null; //volatile防止指令重排序private static Object locker = new Object();public static SingletonLazy getInstance() {if (instance == null) { //后續線程可以防止重復加鎖synchronized (locker) { //使得if和創建實例變成一個整體的原子操作,防止多個線程同時操作時創建多個實例if (instance == null) { //判斷是否有實例創建了instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}
public class Demo20 { //懶漢模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}
??感謝觀看~~對你有幫助的話給博主點個大大的贊吧(●'?'●)謝謝~~~??