死鎖:
A線程持有 鎖1,接下來要獲取鎖2;與此同時,B線程持有鎖2,要獲取鎖1。兩個線程都在等對方釋放自己需要的鎖,這時兩方會永遠等待下去,就形成了死鎖。
死鎖的四個必要條件:
1.互斥:資源(鎖)同時只能被一個線程占用。
2.占有且等待:線程已經占用資源A,同時等待資源B時,不釋放資源A。
3.不可搶占:其他線程不能強行獲取當前線程占有的資源
4.循環等待:存在一個等待鏈,即T1等待T2占有的資源,T2等待T3占有的資源,T3等待T1占有的資源。
如果要解決死鎖,則需要破壞任意一死鎖的必要條件。
一.破壞占有且等待條件
解決方法:只要限定所有資源鎖同時獲取,同時釋放。就可以預防掉死鎖。其實就是破壞掉占有且等待條件。
下面以銀行轉賬的代碼為例子

/** * 鎖分配類(單例) * * @author Liumz * @since 2019-04-02 15:57:32 */ @Component public class Allocator { /** * 已被申請鎖的集合 */ private List<Object> locks = new ArrayList<>(); /** * 申請鎖 * * @param timeOut 過期時間(秒) * @param lockArray 要申請的鎖集合 */ public synchronized void apply(int timeOut, Object... lockArray) throws Exception { //如果當前不滿足申請條件,則等待。直到資源被釋放時進行notifyAll喚醒當前線程 //while(condition){wait()} 是一個標准范式,線程如果被喚醒,執行時會再判斷一次條件。 LocalDateTime dtStart = LocalDateTime.now(); while (Arrays.stream(lockArray).anyMatch(i -> this.locks.contains(i))) { //時間間隔達到5秒還未獲取到條件鎖,則拋出異常 if (Duration.between(dtStart, LocalDateTime.now()).toMillis() > timeOut * 1000) { throw new Exception("放棄任務"); } //釋放當前對象鎖,並等待 try { this.wait(1000); } catch (InterruptedException ignore) { } } //如果已被申請鎖的集合中沒有要申請的鎖,表示申請成功,並把申請成功的鎖加入集合 this.locks.addAll(Arrays.asList(lockArray)); } /** * 釋放鎖 * * @param lockArray 要釋放的鎖集合 */ public synchronized void free(Object... lockArray) { for (Object o : lockArray) { this.locks.remove(o); } //喚醒所有wait的線程,正在等待locks被移除釋放的線程。盡量使用notifyAll,避免有的線程會不被喚醒,一直wait this.notifyAll(); } }

/** * 銀行賬戶類 * * @author Liumz * @since 2019-04-02 15:36:15 */ @Component @Scope("prototype") public class BankAccount { /** * 余額 */ private int balance; /** * 鎖分配對象 */ @Autowired private Allocator allocator; /** * 轉賬 * * @param target 目標賬戶 * @param amount 轉賬金額 */ public void transfer(BankAccount target, int amount) { //申請鎖,如果申請不到會一直等待。除非超時時拋出異常 try { this.allocator.apply(5, target, this); } catch (Exception e) { return; } try { //同時鎖定目標和當前賬戶,避免出現死鎖情況.並且進行賬戶余額加減操作 synchronized (this) { synchronized (target) { this.balance -= amount; target.balance += amount; } } } finally { //同時釋放加的兩個鎖 this.allocator.free(target, this); } } }
二.破壞循環等待條件
解決方法:對鎖進行排序,每次申請鎖需要按從小到大順序申請。這樣就不存在循環等待了

/** * 銀行賬戶類 * * @author Liumz * @since 2019-04-02 15:36:15 */ @Component @Scope("prototype") public class BankAccount { /** * 余額 */ private int balance; /** * 序號id */ private int id; /** * 轉賬 * * @param target 目標賬戶 * @param amount 轉賬金額 */ public void transfer(BankAccount target, int amount) { //對賬戶序號排序 BankAccount firstLock = target; BankAccount secondLock = this; if (firstLock.id > secondLock.id) { firstLock = this; secondLock = target; } //先鎖定序號小的賬戶,再鎖定序號大的賬戶 synchronized (firstLock){ synchronized (secondLock){ this.balance -= amount; target.balance += amount; } } } }
三.破壞不可搶占條件
解決方法: 使用 Lock 和UnLock,在finally里執行unlock,主動釋放資源。此時別人就可以搶占了。
活鎖:
多個線程獲取不到資源,就放開已獲得的資源,重試。相當於系統空轉,一直在做無用功。
例如,行人走路相向而行,互相謙讓,一直重復謙讓的過程。
如以下一直死循環:
start: p1 lock A p2 lock B p1 lock B failed p2 lock A failed p1 release A p2 release B goto start
解決方法:引入一些隨機性,比如暫停隨機時間重試。
飢餓:
1:優先級高的線程總是搶占到資源,而優先級低的線程可能會一直等待,從而無法獲取資源無法執行;
2:一個線程一直不釋放資源,別的線程也會出現飢餓的情況。
3:wait()等待情況下的線程一直都不被notify,而其他的線程總是能被喚醒
解決方法:引入公平鎖
無鎖:
CAS(campare and swap):內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,將內存值修改為B並返回true,否則什么都不做並返回false。CAS是原子操作,只有一條cpu指令
無鎖即不對資源鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功。一個修改操作在一個循環內進行,線程會不斷的嘗試修改共享資源,如果沒有沖突(CAS判斷)就修改成功並退出否則就會繼續下一次循環嘗試。
如jdk的基於CAS實現的原子操作類,就是對無鎖的實現。 還有無鎖隊列,也是循環線程對變量進行CAS操作的數據結構。
CAS的缺點:
1.ABA問題:V值為A,T1,T2從內存取出V值為A.。然后T2 CAS修改變量V為B , 接着T2 又CAS修改變量V為A。這時T1 CAS 變量V時發現內存中V還是A ,CAS操作成功。
2.循環消耗大
3.只能保證一個共享變量的原子操作