雙重檢查鎖定問(wèn)題:Double-checked Locking
1、先來(lái)看問(wèn)題代碼

- 代碼
// 單例模式構(gòu)建對(duì)象
public static LocalCache getInstance(){
// 雙重檢測(cè)鎖,提高運(yùn)行效率
if (instance == null){
synchronized (LocalCache.class){
if (instance == null) {
instance = new LocalCache();
}
}
}
return instance;
}
代碼(該方法)的預(yù)期目標(biāo)是使用 懶漢模式 的單例設(shè)計(jì)模式獲取對(duì)象,阿里插件顯示這段代碼是線程不安全的
2、改進(jìn)方案
2.1、懶漢模式改為餓漢模式
這種思路有幾種實(shí)現(xiàn)方式,這里貼一種,是靜態(tài)代碼塊實(shí)例化一次對(duì)象

- 代碼
static {
instance = new LocalCache();
}
// 單例模式構(gòu)建對(duì)象
public static LocalCache getInstance(){
return instance;
}
2.2、同為懶漢模式下的代碼改進(jìn)
可行的方案
instance變量加上 volatile 關(guān)鍵字,保證變量值的可見(jiàn)性
/**
* volatile的可見(jiàn)性,可以確保拿到 instance 的最終值
*/
private static volatile LocalCache instance;
// 單例模式構(gòu)建對(duì)象
public static LocalCache getInstance(){
// 雙重檢測(cè)鎖,提高運(yùn)行效率
if (instance == null){
synchronized (LocalCache.class){
if (instance == null) {
instance = new LocalCache();
}
}
}
return instance;
}
否定的方案(這個(gè)方案也不能保證線程安全,這里有點(diǎn)不太懂)
// 單例模式構(gòu)建對(duì)象
public static LocalCache getInstance(){
// 雙重檢測(cè)鎖,提高運(yùn)行效率
if (instance == null){
synchronized (LocalCache.class){
if (instance == null) {
LocalCache localCache = new LocalCache();
instance = localCache;
}
}
}
return instance;
}
3、雙重檢測(cè)鎖定問(wèn)題產(chǎn)生的原因
3.1、簡(jiǎn)單來(lái)說(shuō)
instance = new LocalCache();這行代碼有三步操作(《碼出高效Java開(kāi)發(fā)手冊(cè)》P233頁(yè)提到有兩步操作,個(gè)人感覺(jué)不太好解釋):
- 初始化 LocalCache 實(shí)例
- 為本來(lái)為null的instance變量開(kāi)辟內(nèi)存空間,并確定默認(rèn)大?。ㄟ@一點(diǎn)《碼出高效》P233頁(yè)書(shū)中并沒(méi)有提到)
- 將對(duì)象地址寫(xiě)進(jìn) instance 字段
這三步操作并不是原子化的
3.2、舉個(gè)例子
- 線程A進(jìn)入到
if(instance == null){的時(shí)候,instance為null - 線程A進(jìn)入同步代碼塊(synchronized括起來(lái)的代碼塊),到
instance = new LocalCache();時(shí)執(zhí)行了 為instance開(kāi)辟內(nèi)存空間 和 將對(duì)象的引用存入內(nèi)存空間 的動(dòng)作,但是沒(méi)有實(shí)例化LocalCache對(duì)象 - 線程B執(zhí)行到
if(instance == null){的時(shí)候,instance不為null(但是實(shí)際沒(méi)有指向某個(gè)堆內(nèi)的內(nèi)存,簡(jiǎn)而言之,這塊內(nèi)存空間(棧的內(nèi)存空間)的引用地址指向的(堆的)內(nèi)存空間中沒(méi)有實(shí)際對(duì)象),所以直接return了一個(gè)中間態(tài)(我自己起的名字。。)的instance - 線程B中,接下來(lái)的代碼邏輯中,拿到instance的值其實(shí)是有問(wèn)題的(有啥問(wèn)題?-TODO- 反正是有問(wèn)題的-_-)
這篇blog有相關(guān)介紹
所以這里涉及到指令重排的問(wèn)題(可能也有叫“指令優(yōu)化”的-《碼出高效》P232-P232有提到),即#3.1的三步操作,CPU在執(zhí)行的時(shí)候并不會(huì)根據(jù)代碼里理解的順序(從上到下、從左到右)執(zhí)行,會(huì)判斷怎樣的組合可以提高效率,重新排列指令執(zhí)行的順序(如圖)
指令重排/指令優(yōu)化 導(dǎo)致的線程安全問(wèn)題
3.3、使用了volatile之后
這里用到的是volatile的防止指令重排的能力(JDK1.5之后才有的)-- volatile還有一個(gè)
可見(jiàn)性的能力,這里貌似沒(méi)有體現(xiàn)(下篇文章探討volatile 可見(jiàn)性/指令重排 問(wèn)題)
- 線程A進(jìn)入到
if(instance == null){的時(shí)候,instance為null - 線程A進(jìn)入同步代碼塊(synchronized括起來(lái)的代碼塊),到
instance = new LocalCache();時(shí)執(zhí)行了 為instance開(kāi)辟內(nèi)存空間 和 實(shí)例化LocalCache對(duì)象 的動(dòng)作,但是沒(méi)有將對(duì)象的引用存入內(nèi)存空間 - 線程B執(zhí)行到
if(instance == null){的時(shí)候,instance為null - 接下去就是預(yù)期的執(zhí)行流程了
4、復(fù)現(xiàn)雙重檢測(cè)問(wèn)題的方式-供參考
??!實(shí)際復(fù)現(xiàn)過(guò)程中并沒(méi)有復(fù)現(xiàn)問(wèn)題,嚴(yán)重懷疑是復(fù)現(xiàn)方式還可以改進(jìn),以下復(fù)現(xiàn)方式僅供參考
懶漢模式加載的單例對(duì)象類
public class LocalCache {
private static LocalCache instance;
// 構(gòu)造方法私有化,防止實(shí)例化
private LocalCache() {}
// 單例模式構(gòu)建對(duì)象
public static LocalCache getInstance() throws InterruptedException {
// // 雙重檢測(cè)鎖,提高運(yùn)行效率
if (instance == null){
synchronized (LocalCache.class){
if (instance == null) {
instance = new LocalCache();
}
}
}
return instance;
}
}
建了兩個(gè)線程工廠,每個(gè)工廠里面有兩根線程(總共4根),模擬多線程環(huán)境(有更簡(jiǎn)便的寫(xiě)法)
public class TestJava {
public static void main(String[] args) {
BlockingQueue blockingDeque = new LinkedBlockingDeque(2);
TestThreadFactory firstFactory = new TestThreadFactory("第一個(gè)線程池");
TestThreadFactory secondFactory = new TestThreadFactory("第二個(gè)線程池");
TestRejectHandler testRejectHandler = new TestRejectHandler();
ThreadPoolExecutor firstThreadPool = new ThreadPoolExecutor(2, 2, Integer.MAX_VALUE, TimeUnit.SECONDS, blockingDeque, firstFactory, testRejectHandler);
ThreadPoolExecutor secondThreadPool = new ThreadPoolExecutor(2, 2, Integer.MAX_VALUE, TimeUnit.SECONDS,
blockingDeque, secondFactory, testRejectHandler);
Task task = new Task();
for (int i = 0; i < 2; i++){
firstThreadPool.execute(task);
secondThreadPool.execute(task);
}
}
/**
* 線程工廠
*/
public static class TestThreadFactory implements ThreadFactory{
private final String namePrefix;
private final AtomicInteger nextId = new AtomicInteger(1);
public TestThreadFactory(String namePrefix) {
this.namePrefix = "TestThreadFactory's " + namePrefix + "-worker-";
}
@Override
public Thread newThread(Runnable task) {
String name = namePrefix + nextId.getAndIncrement();
Thread thread = new Thread(null, task, name, 0);
System.out.println(thread);
return thread;
}
}
/**
* 實(shí)際執(zhí)行任務(wù)
*/
public static class Task implements Runnable{
private final AtomicLong count = new AtomicLong(0L);
@Override
public void run() {
try {
LocalCache instance = LocalCache.getInstance();
// todo 這里做一些instance對(duì)象的操作
System.out.println("running_" + count.getAndIncrement() + ", instance: " + instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 當(dāng)線程異常的時(shí)候,可以打印線程異常堆棧
*/
public static class TestRejectHandler implements RejectedExecutionHandler{
@Override
public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) {
System.out.println("task rejected. " + executor.toString());
}
}
}
