1. 程式人生 > >網易Java研發面試官眼中的Java併發——安全性、活躍性、效能

網易Java研發面試官眼中的Java併發——安全性、活躍性、效能

一. 安全性問題

  1. 執行緒安全的本質是正確性,而正確性的含義是程式按照預期執行

  2. 理論上執行緒安全的程式,應該要避免出現可見性問題(CPU快取)、原子性問題(執行緒切換)和有序性問題(編譯優化)

  3. 需要分析是否存線上程安全問題的場景:存在共享資料且資料會發生變化,即有多個執行緒會同時讀寫同一個資料

  4. 針對該理論的解決方案:不共享資料,採用執行緒本地儲存(Thread Local Storage,TLS);不變模式

Ⅰ. 資料競爭

資料競爭(Data Race):多個執行緒同時訪問同一資料,並且至少有一個執行緒會寫這個資料

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);
	}
}
  • 假設count=0,當兩個執行緒同時執行getCount(),都會返回0
  • 兩個執行緒執行getCount()+1,結果都是1,最終寫入記憶體是1,不符合預期,這種情況為竟態條件

Ⅱ. 竟態條件

  1. 竟態條件(Race Condition):程式的執行結果依賴於執行緒執行的順序
  2. 在併發環境裡,執行緒的執行順序是不確定的
    • 如果程式存在竟態條件問題,那麼意味著程式的執行結果是不確定的

1. 轉賬

public class Account {
	private int balance;
	// 非執行緒安全,存在竟態條件,可能會超額轉出
	public void transfer(Account target, int amt) {
		if (balance > amt) {
			balance -= amt;
			target.balance += amt;
		}
	}
}

Ⅲ. 解決方案

面對資料競爭和竟態條件問題,可以通過互斥的方案來實現執行緒安全,互斥的方案可以統一歸為鎖

二. 活躍性問題

活躍性問題:某個操作無法執行下去,包括三種情況:死鎖、活鎖、飢餓

Ⅰ. 死鎖

  1. 發生死鎖後執行緒會相互等待,表現為執行緒永久阻塞
  2. 解決死鎖問題的方法是規避死鎖(破壞發生死鎖的條件之一)
    • 互斥:不可破壞,鎖定目的就是為了互斥
    • 佔有且等待:一次性申請所有需要的資源
    • 不可搶佔:當執行緒持有資源A,並嘗試持有資源B時失敗,執行緒主動釋放資源A
    • 迴圈等待:將資源編號排序,執行緒申請資源時按遞增(或遞減)的順序申請

Ⅱ. 活鎖

  • 活鎖:執行緒並沒有發生阻塞,但由於相互謙讓,而導致執行不下去
  • 解決方案:在謙讓時,嘗試等待一個隨機時間(分散式一致演算法Raft也有采用)

Ⅲ. 飢餓

  1. 飢餓:執行緒因無法訪問所需資源而無法執行下去
    • 執行緒的優先順序是不相同的,在CPU繁忙的情況下,優先順序低的執行緒得到執行的機會很少,可能發生執行緒飢餓
    • 持有鎖的執行緒,如果執行的時間過長(持有的資源不釋放),也有可能導致飢餓問題
  2. 解決方案
    • 保證資源充足
    • 公平地分配資源(公平鎖) – 比較可行
    • 避免持有鎖的執行緒長時間執行

三. 效能問題

  1. 鎖的過度使用可能會導致序列化的範圍過大,這會影響多執行緒優勢的發揮(併發程式的目的就是為了提升效能
  2. 儘量減少序列,假設序列百分比為5%,那麼多核多執行緒相對於單核單執行緒的提升公式(Amdahl定律) S=1/((1-p)+p/n),n為CPU核數,p為並行百分比,(1-p)為序列百分比
  • 假如p=95%,n無窮大,加速比S的極限為20,即無論採用什麼技術,最高只能提高20倍的效能

Ⅰ. 解決方案

  1. 無鎖演算法和資料結構
    • 執行緒本地儲存(Thread Local Storage,TLS)
    • 寫入時複製(Copy-on-write)
    • 樂觀鎖
    • JUC中的原子類
    • Disruptor(無鎖的記憶體佇列)
  2. 減少鎖持有的時間,互斥鎖的本質是將並行的程式序列化,要增加並行度,一定要減少持有鎖的時間
    • 使用細粒度鎖,例如JUC中的ConcurrentHashMap(分段鎖)
    • 使用讀寫鎖,即讀是無鎖的,只有寫才會互斥的

Ⅱ. 效能指標

  1. 吞吐量:在單位時間內能處理的請求數量,吞吐量越高,說明效能越好
  2. 延遲:從發出請求到收到響應的時間,延遲越小,說明效能越好
  3. 併發量:能同時處理的請求數量,一般來說隨著併發量的增加,延遲也會增加,所以延遲一般是基於併發量來說的

寫在最後

  • 第一:看完點贊,感謝您的認可;
  • ...
  • 第二:隨手轉發,分享知識,讓更多人學習到;
  • ...
  • 第三:記得點關注,每天更新的!!!