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

一. 安全性問題

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

  2. 理論上線程安全的程序,應該要避免出現可見性問題(CPU緩存)、原子性問題(線程切換)和有序性問題(編譯優化)算法

  3. 須要分析是否存在線程安全問題的場景:存在共享數據且數據會發生變化,即有多個線程會同時讀寫同一個數據緩存

  4. 針對該理論的解決方案:不共享數據,採用線程本地存儲(Thread Local Storage,TLS);不變模式

Ⅰ. 數據競爭

數據競爭(Data Race):多個線程同時訪問同一數據,而且至少有一個線程會寫這個數據安全

1. addbash

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. 併發量:能同時處理的請求數量,通常來講隨着併發量的增長,延遲也會增長,因此延遲通常是基於併發量來講的

最後

喜歡的能夠關注個人公衆號,java小瓜哥的分享平臺,平時整理的資料都放在裏面了性能

相關文章
相關標籤/搜索