Java併發程式設計的藝術,解讀併發程式設計的優缺點

lihong發表於2019-05-15

併發程式設計的優缺點

使用併發的原因

  • 多核的CPU的背景下,催生了併發程式設計的趨勢,通過 併發程式設計的形式可以將多核CPU的計算能力發揮到極致,效能得到提升

  • 在特殊的業務場景下先天的就適合於併發程式設計。 比如在影像處理領域,一張1024X768畫素的圖片,包含達到78萬6千多個畫素。即時將所有的畫素遍歷一邊都需要很長的時間, 面對如此複雜的計算量就需要充分利用多核的計算的能力。又比如當我們在網上購物時,為了提升響應速度,需要拆分,減庫存, 生成訂單等等這些操作,就可以進行拆分利用多執行緒的技術完成。  面對複雜業務模型,並行程式會比序列程式更適應業務需求,而併發程式設計更能吻合這種業務拆分

併發程式設計的缺點

頻繁的上下文切換

時間片是CPU分配給各個執行緒的時間,因為時間非常短,所以CPU不斷通過切換執行緒,讓我們覺得多個執行緒是同時執行的,時間片一般是幾十毫秒。 而每次切換時,需要儲存當前的狀態起來,以便能夠進行恢復先前狀態,而這個切換時非常損耗效能, 過於頻繁反而無法發揮出多執行緒程式設計的優勢。 通常減少上下文切換可以採用 無鎖併發程式設計 CAS演算法 使用最少的執行緒 使用協程

  • 無鎖併發程式設計:可以參照concurrentHashMap鎖分段的思想,不同的執行緒處理不同段的資料, 這樣在多執行緒競爭的條件下,可以減少上下文切換的時間

  • CAS演算法,利用Atomic下使用CAS演算法來更新資料,使用了樂觀鎖,可以有效的減少一部分不必要的鎖競爭帶來的上下文切換

  • 使用最少執行緒:避免建立不需要的執行緒,比如任務很少,但是建立了很多的執行緒,這樣會造成大量的執行緒都處於等待狀態

  • 協程:在單執行緒裡實現多工的排程,並在單執行緒裡維持多個任務間的切換

由於上下文切換也是個相對比較耗時的操作,所以在《Java併發程式設計的藝術》一書中有過一個實驗,併發累加未必會比序列累加速度要快。 可以使用 Lmbench3測量上下文切換的時長,vmstat測量上下文切換次數

執行緒安全

多執行緒程式設計中最難以把握的就是臨界區執行緒安全問題,稍微不注意就會出現死鎖的情況,一旦產生死鎖就會造成系統功能不可用。

public class DeadLockDemo {
    private static String resource_a = "A";
    private static String resource_b = "B";
    public static void main(String[] args) {
        deadLock();
    }
    public static void deadLock() {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resource_a) {
                    System.out.println("get resource a");
                    try {
                        Thread.sleep(3000);
                        synchronized (resource_b) {
                            System.out.println("get resource b");
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resource_b) {
                    System.out.println("get resource b");
                    synchronized (resource_a) {
                        System.out.println("get resource a");
                    }
                }
            }
        });
        threadA.start();
        threadB.start();
    }
}

那麼,通常可以用如下方式避免死鎖的情況:

  • 避免一個執行緒同時獲得多個鎖;
  • 避免一個執行緒在鎖內部佔有多個資源,儘量保證每個鎖只佔用一個資源;
  • 嘗試使用定時鎖,使用lock.tryLock(timeOut),當超時等待時當前執行緒不會阻塞;
  • 對於資料庫鎖,加鎖和解鎖必須在一個資料庫連線裡,否則會出現解鎖失敗的情況

學習併發中遇到的一些概念

執行緒

執行緒是依附於程式的,  程式是分配資源的最小單位 一個程式可以生成多個執行緒,這些執行緒擁有共享的程式資源 。 就每個執行緒而言,只有很少的獨有資源, 如:控制執行緒執行的執行緒控制塊,保留區域性變數和少數引數的棧空間等。 線上程的生命週期中,它要經過 新建(New)、就緒(Runnable)、執行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態

Java併發程式設計的藝術,解讀併發程式設計的優缺點

 

同步 VS 非同步

同步和非同步通常用來形容一次方法呼叫。

  • 同步呼叫,就是呼叫者必須等待被呼叫的方法結束後,呼叫者後面的程式碼才能執行。
  • 非同步呼叫,就是呼叫者不用管被呼叫方法是否完成,都會繼續執行後面的程式碼,當被呼叫的方法完成後會通知呼叫者。

來個比喻:超市購物和網上購物

同步呼叫,就像在超市購物,如果一件物品沒了,你得等倉庫人員跟你調貨,直到倉庫人員跟你把貨物送過來,你才能去收銀臺付款。

非同步呼叫,就像網購,你在網上付款下單後,什麼事就不用管了,該幹嘛就幹嘛去了,當貨物到達後你收到通知去取就好。

併發與並行

併發和並行的區別就是 一個處理器同時處理多個任務 多個處理器或者是多核的處理器同時處理多個不同的任務

  • 併發是 邏輯上的同時發生
  • 並行 是物理上的同時發生
併發性(concurrency),又稱共行性,是指能處理多個同時性活動的能力,併發事件之間不一定要同一時刻發生。
並行(parallelism)是指同時發生的兩個併發事件,具有併發的含義,而併發則不一定並行。

來個比喻:併發和並行的區別就是一個人同時吃三個饅頭和三個人同時吃三個饅頭。

下圖反映了一個包含8個操作的任務在一個有兩核心的CPU中建立四個執行緒執行的情況。 假設每個核心有兩個執行緒,那麼每個CPU中兩個執行緒會交替併發,兩個CPU之間的操作會並行運算。 單就一個CPU而言兩個執行緒可以解決執行緒阻塞造成的不流暢問題,其本身執行效率並沒有提高, 多CPU的並行運算才真正解決了執行效率問題,這也正是併發和並行的區別。

Java併發程式設計的藝術,解讀併發程式設計的優缺點

 

阻塞和非阻塞

阻塞和非阻塞通常用來 形容多執行緒間的相互影響 。 比如一個執行緒佔有了臨界區資源,那麼其他執行緒需要這個資源就必須進行等待該資源的釋放, 會導致等待的執行緒掛起,這種情況就是阻塞, 而非阻塞就恰好相反,它強調沒有一個執行緒可以阻塞其他執行緒,所有的執行緒都會嘗試地往前執行。

臨界區

臨界區用來表示一種公共資源或者說是共享資料,可以被多個執行緒使用。 但是每個執行緒使用時, 一旦臨界區資源被一個執行緒佔有,那麼其他執行緒必須等待


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69917606/viewspace-2644470/,如需轉載,請註明出處,否則將追究法律責任。

相關文章