java 單例模式容易忽略的細節(jié)
java單例模式
直接講實現(xiàn)單例模式的兩種方法:懶漢式和餓漢式,單例模式的概念自己上網(wǎng)搜吧這里就不講了!
這里會涉及到java中的jvm,如果你沒有這方面的知識,我建議你先去補補,不然會有點迷糊!
首先說說類什么時候進行加載?
java虛擬機沒有進行強制性的約束,但是對于初始化卻嚴格規(guī)定了有且只有4種情況必須先對類進行初始化。
我們要知道的是在類加載的過程中,加載、驗證、準備是在初始化之前完成的,所以進行了初始化,加載、驗證、準備自然就在之前完成了。
然后這四種情況是分別遇到 new 、 getstatic 、 putstatic 和 invokestatic 這四條指令時,如果對應的類沒有初始化,則要對對應的類先進行初始化。
講完類加載時機,就可以講懶漢式和餓漢式了。
直接先說說懶漢式為什么是線程不安全的?
先看最開始的代碼:
public class Student2 {
//1:構(gòu)造私有
private Student2(){}
//2:定義私有靜態(tài)成員變量,先不初始化
private static Student2 student = null;
//3:定義公開靜態(tài)方法,獲取本身對象
public static Student2 getSingletonInstance(){
//沒有對象,再去創(chuàng)建
if (student == null) {
student = new Student2();
}
//有對象就返回已有對象
return student;
}
}
結(jié)合之前講的類加載內(nèi)容,遇到new或加載靜態(tài)方法了就會進行類加載了。
線程1它new了一個對象,線程2它緊接著也new一個對象,第二個對象的值把第一個對象的值覆蓋了,不管new了多少個對象,都會產(chǎn)生垃圾對象,只有最后一個對象才會保持住,其他對象都會變成不可達對象,被垃圾回收,這個過程就相當于產(chǎn)生了大量無效對象,這就是線程不安全的原因!
那為了讓懶漢式變得線程安全,我們要怎么做?
看代碼:
public class Student4 {
private volatile static Student4 student = null;
private Student4() {}
public static Student4 getSingletonInstance() {
if (student == null) {//第一個null判斷,是先大范圍過濾一遍
synchronized (Student4.class) {
if (student == null) {
student = new Student4();
}
}
}
return student;
}
}
這個叫雙重檢查鎖DCL,第一個if先大范圍判斷是不是空值,經(jīng)過synchronized,線程1先進去執(zhí)行完后,線程2才能進去,然后第二個if判斷是否完成創(chuàng)建類的實例,線程1創(chuàng)建完了,線程2就不用創(chuàng)建了。
那為什么要加volatile關(guān)鍵字呢?
因為我們Student student = new Student()的執(zhí)行過程是:
1、new觸發(fā)類加載機制(已經(jīng)被加載過的類不需要再次加載)
2、分配內(nèi)存空間
3、將對象進行初始化4、講對象引用地址賦值給??臻g中的變量但我們JVM中的JIT即時編輯器會對代碼的執(zhí)行過程進行優(yōu)化,把過程變?yōu)?、2、4、3。
這是什么意思呢?就是未經(jīng)初始化直接賦值,這樣就是student直接有值了,但整個對象還未初始化完成,所以這個對象是不完整的,是個未成品。在JVM規(guī)范中,它是一個根本不能用的對象。
到了這個時候,線程1做了這么多事,我們讓它休息會,給CPU稍微停一下,線程2就來了,它就直接得到了對象,但它調(diào)用對象的方法時,就會報錯。雖然這個對象有值,但還未初始化完成。所以我們要加上volatile關(guān)鍵字禁止指令重新排序。
面試重災區(qū)說的差不多了,餓漢式還是要講講。
最后就說說餓漢式為什么沒有線程安全問題?
看代碼:
public class Student1 {
// 2:成員變量初始化本身對象
private static Student1 student = new Student1();
// 構(gòu)造私有
private Student1() {
}
// 3:對外提供公共方法獲取對象
public static Student1 getSingletonInstance() {
return student;
}
public void sayHello(String name) {
System.out.println("hello," + name);
}
}
根據(jù)類加載的東西,在多線程的條件下,線程1先執(zhí)行g(shù)etSingletonInstance()時,就會進行類加載,類的靜態(tài)資源就會進行初始化。根據(jù)JVM安全機制里說的,當一個類被JVM加載的時候,該類的加載是線程安全的,相當于JVM對該過程加鎖了。所以整個過程處于一個鎖的范圍內(nèi),然后靜態(tài)成員變量進行初始化就相當于Student1()被new了,只會被new一次。
當?shù)诙€線程進來,它就發(fā)現(xiàn)這個類已經(jīng)被加載了,就不需要進行加載了,對象也不需要頻繁創(chuàng)建,所以線程是安全的!
總結(jié)
老劉看過很多關(guān)于java單例模式的資料,多多少少都會缺少一點細節(jié),這次老劉把它補全了。
最后,如果覺得有哪里寫的不好或者有錯誤的地方,可以聯(lián)系公眾號:努力的老劉,進行交流。
如果覺得寫的不錯,給老劉點個贊!
以上就是java 單例模式容易忽略的細節(jié)的詳細內(nèi)容,更多關(guān)于java 單例模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
BufferedInputStream(緩沖輸入流)詳解_動力節(jié)點Java學院整理
這篇文章主要為大家詳細介紹了BufferedInputStream緩沖輸入流的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05
SpringCloud gateway如何修改返回數(shù)據(jù)
這篇文章主要介紹了SpringCloud gateway如何修改返回數(shù)據(jù)的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
Java Swing CardLayout卡片布局的實現(xiàn)示例
這篇文章主要介紹了Java Swing CardLayout卡片布局的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-12-12
Intellij IDEA連接Navicat數(shù)據(jù)庫的方法
這篇文章主要介紹了Intellij IDEA連接Navicat數(shù)據(jù)庫的方法,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借價值,需要的朋友可以參考下2021-03-03

