Java併發編程的藝術,解讀併發編程的優缺點

併發編程的優缺點

使用併發的緣由

  • 多核的CPU的背景下,催生了併發編程的趨勢,經過併發編程的形式能夠將多核CPU的計算能力發揮到極致,性能獲得提高算法

  • 在特殊的業務場景下先天的就適合於併發編程。 好比在圖像處理領域,一張1024X768像素的圖片,包含達到78萬6千多個像素。即時將全部的像素遍歷一邊都須要很長的時間, 面對如此複雜的計算量就須要充分利用多核的計算的能力。又好比當咱們在網上購物時,爲了提高響應速度,須要拆分,減庫存, 生成訂單等等這些操做,就能夠進行拆分利用多線程的技術完成。 面對複雜業務模型,並行程序會比串行程序更適應業務需求,而併發編程更能吻合這種業務拆分數據庫

併發編程的缺點

頻繁的上下文切換

時間片是CPU分配給各個線程的時間,由於時間很是短,因此CPU不斷經過切換線程,讓咱們以爲多個線程是同時執行的,時間片通常是幾十毫秒。 而每次切換時,須要保存當前的狀態起來,以便可以進行恢復先前狀態,而這個切換時很是損耗性能, 過於頻繁反而沒法發揮出多線程編程的優點。 一般減小上下文切換能夠採用無鎖併發編程CAS算法使用最少的線程使用協程編程

  • 無鎖併發編程:能夠參照concurrentHashMap鎖分段的思想,不一樣的線程處理不一樣段的數據, 這樣在多線程競爭的條件下,能夠減小上下文切換的時間安全

  • CAS算法,利用Atomic下使用CAS算法來更新數據,使用了樂觀鎖,能夠有效的減小一部分沒必要要的鎖競爭帶來的上下文切換多線程

  • 使用最少線程:避免建立不須要的線程,好比任務不多,可是建立了不少的線程,這樣會形成大量的線程都處於等待狀態併發

  • 協程:在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換異步

因爲上下文切換也是個相對比較耗時的操做,因此在《Java併發編程的藝術》一書中有過一個實驗,併發累加未必會比串行累加速度要快。 可使用Lmbench3測量上下文切換的時長,vmstat測量上下文切換次數ide

線程安全

多線程編程中最難以把握的就是臨界區線程安全問題,稍微不注意就會出現死鎖的狀況,一旦產生死鎖就會形成系統功能不可用。性能

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種狀態

 

同步 VS 異步

同步和異步一般用來形容一次方法調用。

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

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

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

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

併發與並行

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

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

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

下圖反映了一個包含8個操做的任務在一個有兩核心的CPU中建立四個線程運行的狀況。 假設每一個核心有兩個線程,那麼每一個CPU中兩個線程會交替併發,兩個CPU之間的操做會並行運算。 單就一個CPU而言兩個線程能夠解決線程阻塞形成的不流暢問題,其自己運行效率並無提升, 多CPU的並行運算才真正解決了運行效率問題,這也正是併發和並行的區別。

 

阻塞和非阻塞

阻塞和非阻塞一般用來形容多線程間的相互影響。 好比一個線程佔有了臨界區資源,那麼其餘線程須要這個資源就必須進行等待該資源的釋放, 會致使等待的線程掛起,這種狀況就是阻塞, 而非阻塞就剛好相反,它強調沒有一個線程能夠阻塞其餘線程,全部的線程都會嘗試地往前運行。

臨界區

臨界區用來表示一種公共資源或者說是共享數據,能夠被多個線程使用。 可是每一個線程使用時,一旦臨界區資源被一個線程佔有,那麼其餘線程必須等待

相關文章
相關標籤/搜索