java并發(fā)
1. 線程的通信
wait()
使線程進(jìn)入睡眠狀態(tài)
notify()
隨機(jī)喚醒一個等待的線程,它將獲得一次搶奪鎖的機(jī)會
notifyAll()
喚醒所有等待的線程
臨界區(qū): 需要線程同步的代碼區(qū)
$$
運(yùn)行狀態(tài)
\dfrac{ wait() }{}
睡眠狀態(tài)
\dfrac{ notify() }{}
等鎖狀態(tài)
\dfrac{ 獲得鎖 }{}
運(yùn)行狀態(tài)
$$
注意
- 3個方法只能在同步塊里面被調(diào)用
- 使用
while
避免被假喚醒 - 不要使用全局對象,字符串常量作為鎖
public class MywaitNotify{
MonitorObject monitor = new MonitorObject();
boolean wasSignalled = false;
public void doWait() {
synchronized(monitor){
while (!wasSignalled){ //用while代替if防止假喚醒
monitor.wait(); //wait(),notify(),notifyAll()必須位于同步塊內(nèi)
}
wasSignalled = false;
}
}
public void doNotify() {
synchronized(monitor){
wasSignalled = true;
monitor.notify(); //notity后并不意味著等待線程立即可以繼續(xù)執(zhí)行,而只是可以有機(jī)會再次搶奪鎖
}
}
}
2. 饑餓與公平
- 若一個線程因為cpu時間全部被其他線程搶走而得不到cpu運(yùn)行時間,這種狀態(tài)稱之為饑餓
- 公平鎖的實現(xiàn)
public class FairLock {
private boolean locked; //鎖區(qū)是否正處于鎖定狀態(tài)(鎖區(qū)是否已有其他線程存在尚未離開)
private Thread lockingThread; //當(dāng)前在鎖區(qū)的那個線程
private List<LockObject> waittingThreads = new ArrayList<LockObject>();
public void lock() throws InterruptedException{
LockObject lockObject = new LockObject();
boolean isAllowedEnterLockedArea = false;
//線程開始排隊,誰先進(jìn)來,誰先被add進(jìn)List,也就是排在最前面
synchronized (this) {
waittingThreads.add(lockObject);
}
//若不允許進(jìn)入鎖區(qū),則自旋等待
while (!isAllowedEnterLockedArea) {
synchronized (this) {
/* 當(dāng)前線程是否允許進(jìn)入鎖區(qū)的線程(isAllowedEnterLockedArea)?
* 如果鎖區(qū)沒有其他線程(locked=false) 且 排隊排在最前面
* 則當(dāng)前線程是被允許進(jìn)入鎖區(qū)的線程
*/
isAllowedEnterLockedArea = !locked && waittingThreads.get(0) == lockObject;
/* 若允許進(jìn)入鎖區(qū),則在進(jìn)入前將"鎖區(qū)已有線程"的標(biāo)志置為true,
* 并從排隊隊列中將自己移除 */
if (isAllowedEnterLockedArea) {
locked = true;
waittingThreads.remove(lockObject);
lockingThread = Thread.currentThread();
return;
}
}
try {
/* 不被允許進(jìn)入鎖區(qū)的線程(isAllowedEnterLockedArea=false)
* 使用他排隊時使用的lockObject對象作為鎖標(biāo)志的鎖睡眠了,
* 直到有其他線程調(diào)用這個lockObject對象的notify()叫醒他
* -----------------------
* 這里為什么不直接調(diào)用lockObject.wait()方法?
* --為了防止信號丟失!
* 因為自帶的wait()或notify()方法是沒有狀態(tài)的,
* 有可能當(dāng)前線程(線程A)執(zhí)行到此處,在還沒有進(jìn)入睡眠時,
* 其他線程unlock,就已經(jīng)notify了線程A,
* 這時由于線程A不是出于睡眠狀態(tài)而忽略了notify的信號,
* 接著線程A進(jìn)入睡眠, 因為喚醒它的信號已被忽略,
* 那么線程A將永遠(yuǎn)不會醒來
*/
lockObject.doWait();
} catch (InterruptedException e) {
/* 若因出現(xiàn)異常,在等待進(jìn)入鎖區(qū)的睡眠中被異常打斷,
* 則將其從排隊隊列中踢出 */
synchronized (this) {
waittingThreads.remove(lockObject);
}
throw e;
}
}
}
public synchronized void unlock(){
if (this.lockingThread != Thread.currentThread()){
throw new IllegalMonitorStateException("當(dāng)前線程并不是加鎖者,解鎖失敗");
}
locked = false;
lockingThread = null;
//在離開鎖區(qū)時,若還有其他線程在排隊等待進(jìn)入鎖區(qū),則叫醒排在最前面的那個線程
if (waittingThreads.size() > 0){
waittingThreads.get(0).doNotify();
}
}
}
public class LockObject {
private boolean isNotified; //使用成員變量記住信號,防止信號丟失(帶狀態(tài)的鎖)
public synchronized void doWait() throws InterruptedException{
while(!isNotified){
this.wait();
}
isNotified = false;
}
public synchronized void doNotify(){
this.isNotified = true;
this.notify();
}
@Override
public boolean equals(Object o){
return this == o;
}
}
3. 嵌套管程鎖死
有嵌套同步塊A和B,鎖分別是LockA,LockB,線程1在持有外層同步塊A的鎖LockA的情況下,進(jìn)入同步塊B,調(diào)用LockB.wait()進(jìn)入睡眠,等待其他線程調(diào)用LockB.notify()將其喚醒, 至此,線程1就釋放了LockB, 但仍持有LockA. 這時, 其他線程想進(jìn)入鎖同樣為LockA的同步塊去喚醒線程A(調(diào)用LockB.notify),卻發(fā)現(xiàn)因線程1持有鎖LockA睡眠了,無法獲得該鎖,從而導(dǎo)致死鎖狀態(tài)
簡要的歸納就是:
線程帶著其它鎖沒有釋放的情況下進(jìn)入了休眠(因同步塊嵌套導(dǎo)致),導(dǎo)致其他線程無法獲得這個鎖去叫醒他
嵌套管程鎖死的例子:
public class Lock{
private Monitor monitor = new Monitor();
private boolean locked;
public void lock(){
synchronized (this){
while (locked){
synchronized(monitor){
//進(jìn)入休眠時,只釋放了monitor鎖,沒有釋放this鎖
monitor.wait();
}
}
locked = true;
}
}
public void unlock(){
//因為this被占用, 無法進(jìn)入同步塊喚醒已睡眠的線程
synchronized(this){
locked = false;
synchronized(monitor){
monitor.notify();
}
}
}
}
死鎖 與 嵌套管程鎖死 的區(qū)別:
死鎖中,兩個線程都在等待對方釋放鎖.
嵌套管程鎖死中,線程1持有鎖A, 同事等待從線程2發(fā)來的信號,線程2需要鎖A來發(fā)信號給線程1.
4. Slipped Conditions
從一個線程檢查某一特定條件, 到操作此條件期間,這個條件已被其他線程所改變
public class Lock {
private boolean locked;
public void lock() {
synchronized (this){ //同步塊1
while (locked){
this.wait();
}
}
/* 這里同步塊1與同步塊2間可能出現(xiàn)slipped conditions:
* 一個線程(線程A)檢查到locked=false跳出同步塊1后,即將準(zhǔn)備進(jìn)入同步塊2,此時:
* this鎖已被釋放. 這時新的線程B調(diào)用lock()進(jìn)入同步塊1,檢查到locked=false,
* 因此線程B馬上跳出同步塊1,進(jìn)入同步塊2,將locked置為true,
* 接下來線程A才進(jìn)入同步塊2,而此時發(fā)現(xiàn):期間locked已被別人(線程B)修改過了
*/
synchronized(this){ //同步塊2
locked = true;
}
}
public void unlocked() {
locked = false;
this.notify();
}
}
為避免slipped conditions, 條件的檢查和設(shè)置必須是原子的, 也就是在第一個線程檢查和設(shè)置條件期間,不會有其他線程檢查這個條件