
一. 安全性問題
線程安全的本質(zhì)是正確性,而正確性的含義是程序按照預(yù)期執(zhí)行
理論上線程安全的程序,應(yīng)該要避免出現(xiàn)可見性問題(CPU緩存)、原子性問題(線程切換)和有序性問題(編譯優(yōu)化)
需要分析是否存在線程安全問題的場景:存在共享數(shù)據(jù)且數(shù)據(jù)會(huì)發(fā)生變化,即有多個(gè)線程會(huì)同時(shí)讀寫同一個(gè)數(shù)據(jù)
針對該理論的解決方案:不共享數(shù)據(jù),采用線程本地存儲(chǔ)(Thread Local Storage,TLS);不變模式
Ⅰ. 數(shù)據(jù)競爭
數(shù)據(jù)競爭(Data Race):多個(gè)線程同時(shí)訪問同一數(shù)據(jù),并且至少有一個(gè)線程會(huì)寫這個(gè)數(shù)據(jù)
1. add
private static final int MAX_COUNT = 1_000_000;
private long count = 0;
// 非線程安全
public void add() {
int index = 0;
while (++index < MAX_COUNT) {
count += 1;
}
}
2. add + synchronized
private static final int MAX_COUNT = 1_000_000;
private long count = 0;
public synchronized long getCount() {
return count;
}
public synchronized void setCount(long count) {
this.count = count;
}
// 非線程安全
public void add() {
int index = 0;
while (++index < MAX_COUNT) {
setCount(getCount() + 1);
}
}
- 假設(shè)count=0,當(dāng)兩個(gè)線程同時(shí)執(zhí)行g(shù)etCount(),都會(huì)返回0
- 兩個(gè)線程執(zhí)行g(shù)etCount()+1,結(jié)果都是1,最終寫入內(nèi)存是1,不符合預(yù)期,這種情況為竟態(tài)條件
Ⅱ. 竟態(tài)條件
- 竟態(tài)條件(Race Condition):程序的執(zhí)行結(jié)果依賴于線程執(zhí)行的順序
- 在并發(fā)環(huán)境里,線程的執(zhí)行順序是不確定的
- 如果程序存在竟態(tài)條件問題,那么意味著程序的執(zhí)行結(jié)果是不確定的
1. 轉(zhuǎn)賬
public class Account {
private int balance;
// 非線程安全,存在竟態(tài)條件,可能會(huì)超額轉(zhuǎn)出
public void transfer(Account target, int amt) {
if (balance > amt) {
balance -= amt;
target.balance += amt;
}
}
}
Ⅲ. 解決方案
面對數(shù)據(jù)競爭和竟態(tài)條件問題,可以通過互斥的方案來實(shí)現(xiàn)線程安全,互斥的方案可以統(tǒng)一歸為鎖
二. 活躍性問題
活躍性問題:某個(gè)操作無法執(zhí)行下去,包括三種情況:死鎖、活鎖、饑餓
Ⅰ. 死鎖
- 發(fā)生死鎖后線程會(huì)相互等待,表現(xiàn)為線程永久阻塞
- 解決死鎖問題的方法是規(guī)避死鎖(破壞發(fā)生死鎖的條件之一)
- 互斥:不可破壞,鎖定目的就是為了互斥
- 占有且等待:一次性申請所有需要的資源
- 不可搶占:當(dāng)線程持有資源A,并嘗試持有資源B時(shí)失敗,線程主動(dòng)釋放資源A
- 循環(huán)等待:將資源編號排序,線程申請資源時(shí)按遞增(或遞減)的順序申請
Ⅱ. 活鎖
- 活鎖:線程并沒有發(fā)生阻塞,但由于相互謙讓,而導(dǎo)致執(zhí)行不下去
- 解決方案:在謙讓時(shí),嘗試等待一個(gè)隨機(jī)時(shí)間(分布式一致算法Raft也有采用)
Ⅲ. 饑餓
-
饑餓:線程因無法訪問所需資源而無法執(zhí)行下去
- 線程的優(yōu)先級是不相同的,在CPU繁忙的情況下,優(yōu)先級低的線程得到執(zhí)行的機(jī)會(huì)很少,可能發(fā)生線程饑餓
- 持有鎖的線程,如果執(zhí)行的時(shí)間過長(持有的資源不釋放),也有可能導(dǎo)致饑餓問題
-
解決方案
- 保證資源充足
- 公平地分配資源(公平鎖) – 比較可行
- 避免持有鎖的線程長時(shí)間執(zhí)行
三. 性能問題
- 鎖的過度使用可能會(huì)導(dǎo)致串行化的范圍過大,這會(huì)影響多線程優(yōu)勢的發(fā)揮(并發(fā)程序的目的就是為了提升性能)
-
盡量減少串行,假設(shè)串行百分比為5%,那么多核多線程相對于單核單線程的提升公式(Amdahl定律)
S=1/((1-p)+p/n),n為CPU核數(shù),p為并行百分比,(1-p)為串行百分比
- 假如p=95%,n無窮大,加速比S的極限為20,即無論采用什么技術(shù),最高只能提高20倍的性能
Ⅰ. 解決方案
- 無鎖算法和數(shù)據(jù)結(jié)構(gòu)
- 線程本地存儲(chǔ)(Thread Local Storage,TLS)
- 寫入時(shí)復(fù)制(Copy-on-write)
- 樂觀鎖
- JUC中的原子類
- Disruptor(無鎖的內(nèi)存隊(duì)列)
- 減少鎖持有的時(shí)間,互斥鎖的本質(zhì)是將并行的程序串行化,要增加并行度,一定要減少持有鎖的時(shí)間
- 使用細(xì)粒度鎖,例如JUC中的ConcurrentHashMap(分段鎖)
- 使用讀寫鎖,即讀是無鎖的,只有寫才會(huì)互斥的
Ⅱ. 性能指標(biāo)
- 吞吐量:在單位時(shí)間內(nèi)能處理的請求數(shù)量,吞吐量越高,說明性能越好
- 延遲:從發(fā)出請求到收到響應(yīng)的時(shí)間,延遲越小,說明性能越好
- 并發(fā)量:能同時(shí)處理的請求數(shù)量,一般來說隨著并發(fā)量的增加,延遲也會(huì)增加,所以延遲一般是基于并發(fā)量來說的
寫在最后
- 第一:看完點(diǎn)贊,感謝您的認(rèn)可;
- ...
- 第二:隨手轉(zhuǎn)發(fā),分享知識,讓更多人學(xué)習(xí)到;
- ...
- 第三:記得點(diǎn)關(guān)注,每天更新的?。?!
- ...