剛剛來新公司第美,發(fā)現(xiàn)項目中大量的單例使用的雙重檢查鎖方式的單例虎韵,但是很奇怪并沒有加volatile修飾詞雁佳。
認真復(fù)習(xí)了一下volatile參數(shù),發(fā)現(xiàn)這個確實有必要加的筋搏。但是究竟不加volatile會不會出現(xiàn)問題呢仆百?
按照已知的理解,new一個新對象時奔脐,出現(xiàn)一下原子操作:
- 分配內(nèi)存空間俄周。
- 初始化對象。
- 將內(nèi)存空間的地址賦值給對應(yīng)的引用髓迎。
如果不使用volatile參數(shù)的話峦朗,可能會出現(xiàn)1-3-2的執(zhí)行順序,這樣就會導(dǎo)致在判斷instance == null時排龄,instance不為空波势,直接返回了instance,但實際上這個instance仍然為null的橄维。
使用了volatile這個參數(shù)尺铣,會禁止重排序,是以上過程按照1-2-3的順序執(zhí)行争舞。
然后我使用CountDownLatch模擬了一個高并發(fā)凛忿,測試是否有instance == null為null的情況出現(xiàn)。
很遺憾竞川,在mac上線程加到2000個嘗試同時喚醒(實際上并不會這么多店溢,因為cpu有限),安卓嘗試300個線程同時喚醒流译,反復(fù)好多次逞怨,仍然沒有出現(xiàn)為null的情況者疤。
和旁邊技術(shù)不錯的同事聊了聊福澡,認為,這種情況驹马,需要在配置高的服務(wù)器(cpu多核)并且大量并發(fā)時可能出現(xiàn)革砸,安卓機或者我這個低配mac上除秀,出現(xiàn)概率較小。
算是一次失敗的測試吧算利。記錄一下册踩。
參考文章:知乎討論
附上測試類:
public class Singleton {
//故意沒有加 volatile參數(shù)
private static Singleton instance;
private Singleton() {
}
//雙重加鎖單例模式
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
init();
}
/**
* 線程數(shù)量
*/
public static final int THREAD_NUM = 1000;
/**
* 開始時間
*/
private static long startTime = 0L;
private static volatile int num = 0;
public static void init() {
try {
startTime = System.currentTimeMillis();
System.out.println("CountDownLatch started at: " + startTime);
// 初始化計數(shù)器為1
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < THREAD_NUM; i++) {
new Thread(new Run(countDownLatch)).start();
}
// 啟動多個線程
countDownLatch.countDown();
} catch (Exception e) {
System.out.println("Exception: " + e);
}
}
/**
* 線程類
*/
private static class Run implements Runnable {
private final CountDownLatch startLatch;
public Run(CountDownLatch startLatch) {
this.startLatch = startLatch;
}
@Override
public void run() {
try {
// 線程等待
startLatch.await();
// 執(zhí)行操作
Singleton instance = getInstance();
if (instance == null) {
System.out.print("Thread.currentThread() =" + Thread.currentThread().getId() + (instance == null ? "instance = null" : " " + "\n"));
}
// num = num + 1;
// System.out.print("num = " + num + "\n");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}