1. 單例模式 java 雙重鎖用synchronized修飾之後還用volatile嗎
沒有volatile修飾的uniqueInstance
[java] view plain
public class Singleton {
private static Singleton uniqueInstance;
private Singleton(){
}
public static Singleton getInstance(){
if(uniqueInstance == null){ //#1
synchronized(Singleton.class){ //#2
if(uniqueInstance == null){ //#3
uniqueInstance = new Singleton(); //#4
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is initalized..."); //#5.1
} else {
System.out.println(Thread.currentThread().getName() + ": uniqueInstance is not null now..."); //#5.2
}
}
}
return uniqueInstance;
}
}
這樣可能會導致結果 Singleton被實例化兩次 ,這樣就不符合單例的特點
原因分析:
1. thread2進入#1, 這時子線程的uniqueInstance都是為空的,thread2讓出CPU資源給thread3
2. thread3進入#1, 這時子線程的uniqueInstance都是為空的, thread3讓出CPO資源給thread2
3. thread2會依次執行#2,#3,#4, #5.1,最終在thread2裡面實例化了uniqueInstance。thread2執行完畢讓出CPO資源給thread3
4. thread3接著#1跑下去,跑到#3的時候,由於#1裡面拿到的uniqueInstance還是空(並沒有及時從thread2裡面拿到最新的),所以thread3仍然會執行#4,#5.1
5. 最後在thread2和thread3都實例化了uniqueInstance
例子2:用volatile修飾的uniqueInstance
這里就不貼重復的代碼了,因為只是加多一個volatile來修飾成員變數:uniqueInstance,
這樣可以創建出一個單例實例。
原因分析:
volatile(java5):可以保證多線程下的可見性;
讀volatile:每當子線程某一語句要用到volatile變數時,都會從主線程重新拷貝一份,這樣就保證子線程的會跟主線程的一致。
寫volatile: 每當子線程某一語句要寫volatile變數時,都會在讀完後同步到主線程去,這樣就保證主線程的變數及時更新。
1. thread2進入#1, 這時子線程的uniqueInstance都是為空的(java內存模型會從主線程拷貝一份uniqueInstance=null到子線程thread2),thread2讓出CPU資源給thread3
2. thread3進入#1, 這時子線程的uniqueInstance都是為空的(java內存模型會從主線程拷貝一份uniqueInstance=null到子線程thread2), thread3讓出CPO資源給thread2
3. thread2會依次執行#2,#3,#4, #5.1,最終在thread2裡面實例化了uniqueInstance(由於是volatile修飾的變數,會馬上同步到主線程的變數去)。thread2執行完畢讓出CPO資源給thread3
4. thread3接著#1跑下去,跑到#3的時候,會又一次從主線程拷貝一份uniqueInstance!=null回來,所以thread3就直接跑到了#5.2
5. 最後在thread3不再會重復實例化uniqueInstance了
2. java單例雙重檢查鎖為什麼需要加volatile關鍵字
已經修改,的確應該加上volatile關鍵字。不加的情況下,假設兩個線程,線程A正在執行instance = new Instance()的操作,而線程B開始執行if(instance==null)的判斷,當不存在volatile的時候,因為 new Instance()是一個非原子操作,可能發生無序寫入,構造函數可能在整個對象構造完成前執行完畢,線程B可能會看到一個不完整的instance對象,因為java的某些實現會在內存中開辟一片存儲對象的區域後直接返回內存的引用,所以線程B判斷不為null,而這時候實際上,instance的構造函數還沒有執行,從而線程b得到不完整的對象。在 Instance 的構造函數執行之前,會在內存中開辟一片存儲對象的區域後直接返回內存的引用,賦值給變數 instance,instance也就可能成為非 null 的,即賦值語句在對象實例化之前調用,此時別的線程得到的是一個還會初始化的對象,這樣會導致系統崩潰線程B可能會看到一個不完整的instance對象,因為java的某些實現,所以線程B判斷不為null。從而得到不完整的對象。
3. Java單例模式雙重檢驗鎖的第一次是否為空判斷是什麼目的
第一次判斷是否為空是位了保證是單例,只有初始是空的情況下才可以創建,synchronize里頭的if判空是為了避免小概率事件發生,比如當對象還沒創建時,有兩個線程都通過了外部的判空,進入synchronize入口處,此時由於同步加鎖,只有一個線程可以執行synchronize內部的代碼(生成了單例對象),當它執行完釋放了鎖後,第二個線程就進入的synchronize內部的代碼,如果此時不再判斷一下的話,該對象就再次被創建了。
4. 如何在Java中使用雙重檢查鎖實現單例
一個常見情景,單例類在多線程環境中違反契約。如果你要一個新手寫出單例模式,可能會得到下面的代碼:
private static Singleton _instance;
public static Singleton getInstance() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
然後,當你指出這段代碼在超過一個線程並行被調用的時候會創建多個實例的問題時,他很可能會把整個getInstance()方法設為同步(synchronized),就像我們展示的第二段示例代碼getInstanceTS()方法一樣。盡管這樣做到了線程安全,並且解決了多實例問題,但並不高效。在任何調用這個方法的時候,你都需要承受同步帶來的性能開銷,然而同步只在第一次調用的時候才被需要,也就是單例類實例創建的時候。這將促使我們使用雙重檢查鎖模式(double checked locking pattern),一種只在臨界區代碼加鎖的方法。程序員稱其為雙重檢查鎖,因為會有兩次檢查 _instance == null,一次不加鎖,另一次在同步塊上加鎖。這就是使用Java雙重檢查鎖的示例:
public static Singleton getInstanceDC() {
if (_instance == null) { // Single Checked
synchronized (Singleton.class) {
if (_instance == null) { // Double checked
_instance = new Singleton();
}
}
}
return _instance;
}
這個方法表面上看起來很完美,你只需要付出一次同步塊的開銷,但它依然有問題。除非你聲明_instance變數時使用了volatile關鍵字。沒有volatile修飾符,可能出現Java中的另一個線程看到個初始化了一半的_instance的情況,但使用了volatile變數後,就能保證先行發生關系(happens-before relationship)。對於volatile變數_instance,所有的寫(write)都將先行發生於讀(read),在Java 5之前不是這樣,所以在這之前使用雙重檢查鎖有問題。現在,有了先行發生的保障(happens-before guarantee),你可以安全地假設其會工作良好。另外,這不是創建線程安全的單例模式的最好方法,你可以使用枚舉實現單例模式,這種方法在實例創建時提供了內置的線程安全。另一種方法是使用靜態持有者模式(static holder pattern)。
/*
* A journey to write double checked locking of Singleton class in Java.
*/
class Singleton {
private volatile static Singleton _instance;
private Singleton() {
// preventing Singleton object instantiation from outside
}
/*
* 1st version: creates multiple instance if two thread access
* this method simultaneously
*/
public static Singleton getInstance() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
/*
* 2nd version : this definitely thread-safe and only
* creates one instance of Singleton on concurrent environment
* but unnecessarily expensive e to cost of synchronization
* at every call.
*/
public static synchronized Singleton getInstanceTS() {
if (_instance == null) {
_instance = new Singleton();
}
return _instance;
}
/*
* 3rd version : An implementation of double checked locking of Singleton.
* Intention is to minimize cost of synchronization and improve performance,
* by only locking critical section of code, the code which creates instance of Singleton class.
* By the way this is still broken, if we don't make _instance volatile, as another thread can
* see a half initialized instance of Singleton.
*/
public static Singleton getInstanceDC() {
if (_instance == null) {
synchronized (Singleton.class) {
if (_instance == null) {
_instance = new Singleton();
}
}
}
return _instance;
}
}
這就是本文的所有內容了。這是個用Java創建線程安全單例模式的有爭議的方法,使用枚舉實現單例類更簡單有效。我並不建議你像這樣實現單例模式,因為用Java有許多更好的方式。但是,這個問題有歷史意義,也教授了並發是如何引入一些微妙錯誤的。正如之前所說,這是面試中非常重要的一點。
在去參加任何Java面試之前,要練習手寫雙重檢查鎖實現單例類。這將增強你發現Java程序員們所犯編碼錯誤的洞察力。另外,在現在的測試驅動開發中,單例模式由於難以被模擬其行為而被視為反模式(anti pattern),所以如果你是測試驅動開發的開發者,最好避免使用單例模式。轉載,僅供參考。
5. 在java中雙重檢查加鎖為什麼會失效
我個人認為既然是寫的問題,可能對於程序來說同步代碼塊結束了,鎖也就釋放了,但是new這個過程是在計算機中真實存在的,因為是線程問題,如果在一個線程就不會出這種問題,總是建立好空間之後再運行下面,但是線程可能有極端情況這邊還沒建立好,那邊已經開始讀了。這樣解釋說得通。
6. JAVA考試試題:寫一個帶雙鎖機制的單例模式~ 160分!謝謝了,這個題我實在沒有看懂,也做不來,忘大家
雙重鎖機制
namespaceSingleton
{
publicclassSingleton
{
//定義一個私有的靜態全局變數來保存該類的唯一實例
;
//定義一個只讀靜態對象
//且這個對象是在程序運行時創建的
=newobject();
///
///構造函數必須是私有的
///這樣在外部便無法使用new來創建該類的實例
///
privateSingleton()
{}
///
///定義一個全局訪問點
///設置為靜態方法
///則在類的外部便無需實例化就可以調用該方法
///
///
()
{
//這里可以保證只實例化一次
//即在第一次調用時實例化
//以後調用便不會再實例化
//第一重singleton==null
if(singleton==null)
{
lock(syncObject)
{
//第二重singleton==null
if(singleton==null)
{
singleton=newSingleton();
}
}
}
returnsingleton;
}
}
}
7. java中的lock鎖,多次獲得,只unlock一次,會有什麼效果
摘要 Lock鎖介紹:
8. 如何在Java中使用雙重檢查鎖實現單例
public class SingleDemo {
private static SingleDemo s = null;
private SingleDemo(){}
public static SingleDemo getInstance(){
/*如果第一個線程獲取到了單例的實例對象,
* 後面的線程再獲取實例的時候不需要進入同步代碼塊中了*/
if(s == null){
//同步代碼塊用的鎖是單例的位元組碼文件對象,且只能用這個鎖
synchronized(SingleDemo.class){
if(s == null){
s = new SingleDemo();
}
}
}
return s;
}
}
用這種方式解決了懶漢式的線程安全問題,也提高了效率,但是在實際開發中還是用餓漢式的比較多,畢竟這個代碼比較多,比較繁瑣。