????????如下圖代碼所示,生產(chǎn)者Producer類和消費者Consumer類共同操作同一個資源resource,二者產(chǎn)生了如下所示的線程安全問題


分析:
? ? ? ? 上面的例子中,我們開啟了4個線程,其中線程t1,t2是生產(chǎn)者。t3,t4是消費者

? ??(1)線程t1正常生產(chǎn)后進(jìn)入阻塞狀態(tài) ??
????????假設(shè)開始的時候:t1進(jìn)入resource類中,flag初始值為false,t1直接給name賦值,然后執(zhí)行notify()(線程池此時為空)。此時t1還處于運行狀態(tài),因此run方法還會從頭再執(zhí)行,而此時flag變?yōu)閠rue,t1進(jìn)入阻塞狀態(tài)wait()
? ??(2)線程t2進(jìn)入阻塞狀態(tài)
? ? ? ? 當(dāng)線程t1進(jìn)入阻塞狀態(tài)后,此時t2,t3,t4線程都處于就緒狀態(tài)。這里我們假設(shè)t2進(jìn)入運行狀態(tài),此時flag為真,t2也進(jìn)入阻塞狀態(tài)wait()
? ??(3)線程t3正常消費后進(jìn)入阻塞狀態(tài),并喚醒線程t1
? ? ? ? t3正常消費,喚醒線程池中的t1(此時t1處于就緒狀態(tài))t3還處于運行狀態(tài),flag變?yōu)閒alse,t3進(jìn)入阻塞狀態(tài)wait()
????(4)線程t1與線程t4爭搶執(zhí)行權(quán),t4進(jìn)入阻塞狀態(tài)
? ? ? ? 由于flag為false,線程t4進(jìn)入阻塞狀態(tài)。線程t1開始執(zhí)行
????(5)t1正常生產(chǎn),喚醒最早進(jìn)入線程池中的t2,并進(jìn)入阻塞狀態(tài)
????(6)t2異常生產(chǎn)
????????此時線程中僅剩t2處于就緒狀態(tài),t2不會重新判斷flag,而是繼續(xù)執(zhí)行wait()之后的代碼 ? ?

解決方案1:追加自旋鎖
? ? ? ? 判斷flag真假可以使用 while (condition) {},不能使用 if(condition) {}。其中 while(condition)循環(huán),它又被叫做“自旋鎖”。為防止該線程沒有收到notify()調(diào)用也從wait()中返回(也稱作虛假喚醒),這個線程會重新去檢查condition條件以決定當(dāng)前是否可以安全地繼續(xù)執(zhí)行還是需要重新保持等待,而不是認(rèn)為線程被喚醒了就可以安全地繼續(xù)執(zhí)行了。被喚醒的線程會自旋直到自旋鎖(while循環(huán))里的條件變?yōu)閒alse(這種做法要慎重,目前的JVM實現(xiàn)自旋會消耗太大的CPU)

解決方案2: JDK1.5版本新特性
????????ReentrantLock(重入鎖):效果synchronized一樣,都可以同步執(zhí)行,lock方法獲得鎖,unlock方法釋放鎖
????????Condition類的awiat方法和Object類的wait方法等效
????????Condition類的signal方法和Object類的notify方法等效
????????Condition類的signalAll方法和Object類的notifyAll方法等效
