Java併發編程的藝術-第一章 併發編程的挑戰

1.1 上下文切換

其實即使是單核處理器也支持多線程執行代碼,CPU 通過給每個線程分配 CPU 時間片來實現這個機制。時間片是 CPU 分配給各個線程的時間,因爲時間片非常短,所以 CPU 通過不停地切換線程執行,讓我們感覺多個線程是同時執行的。而 CPU 切換時間片時,需要保存當前任務的上下文用以恢復本任務時可以繼續執行下去。所以任務從保存到再加載的過程就是一次上下文切換。

1.1.1 多線程一定快嗎

當每個線程任務比較簡單時,切換上下文的代價反而比執行任務的時間長,這樣子多線程就不一定比單線程要快。但是大部分情況併發是比單線程的串行要快的,任務越複雜多線程的性價比越高。

1.1.2 測試上下文切換次數和時長

1.1.3 如何減少上下文切換

三種方式:

  • 無鎖併發編程:我們知道多線程競爭鎖時,會引起上下文切換。所以可以採用一些方式來避免上鎖。
  • CAS 算法:Compare And Swap 算法,不上鎖,通過比較和交換和自旋來保證數據一致性。所以會減少上下文切換。
  • 使用最少線程:切換線程避免不了切換上下文,所以簡單粗暴的把無用的併發幹掉即可減少切換上下文。
  • 協程(纖程):在單線程裏實現多任務的調度,並在單線程裏維持多個任務間的切換。

1.1.4 減少上下文切換實戰

1.2 死鎖

所謂死鎖很簡單,就是 兩個線程互相等待對方的鎖,就死鎖了。話不多說上代碼來看下:

public class Test {

    public static final  String A = "A";
    public static final  String B = "B";

    public static void main(String[] args) {
        new Thread(()->{
            synchronized (A) {
                System.out.println("Thread A lock A ");
                try {
                    TimeUnit.SECONDS.sleep(1L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (B) {
                    System.out.println("Thread A lock B ");
                }
            }
        }, "Thread A").start();

        new Thread(() -> {
            synchronized (B) {
                System.out.println("Thread B lock B ");
                synchronized (A) {
                    System.out.println("Thread B lock A ");
                }
            }

        }, "Thread B").start();
    }
}

結果相信大家都瞭然於心,如圖:
死鎖
死鎖的原因很簡單,A 線程先鎖了A,然後睡了一秒去搶佔 B 鎖,但是這一秒鐘內線程 B 把 B 鎖住了。那麼線程 A 就只能等線程 B 釋放 B 之後才能繼續下去,但是線程 B 的下一步操作需要 A 鎖,於是就形成了互相等待,即死鎖。
在現實中你可能不會寫出這樣的代碼。但是,在一些更爲複雜的場景中,你可能會遇到這樣的問題,比如線程 A 拿到鎖之後,因爲一些異常沒有釋放鎖(死循環)等。介紹避免死鎖的幾個常見方法:

  • 避免一個線程同時獲取多個鎖。
  • 避免一個線程在鎖內同時佔用多個資源,儘量保證每個鎖只佔用一個資源。
  • 嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制。
  • 對於數據庫鎖,加鎖和解鎖必須在一個數據庫連接裏,否則會出現解鎖失敗的情況。

1.3 資源限制的挑戰

  1. 什麼是資源限制:資源限制是指在進行併發編程時,程序的執行速度受限於計算機硬件資源或軟件資源。例如,服務器的帶寬只有2Mb/s,某個資源的下載速度是1Mb/s每秒,系統啓動10個線程下載資源,下載速度不會變成10Mb/s,所以在進行併發編程時,要考慮這些資源的限制。硬件資源限制有帶寬的上傳/下載速度、硬盤讀寫速度和CPU的處理速度。軟件資源限制有數據庫的連接數和socket連接數等。
  2. 資源限制引發的問題:在併發編程中,將代碼執行速度加快的原則是將代碼中串行執行的部分變成併發執行,但是如果將某段串行的代碼併發執行,因爲受限於資源,仍然在串行執行,這時候程序不僅不會加快執行,反而會更慢,因爲增加了上下文切換和資源調度的時間。
  3. 如何解決資源限制的問題:對於硬件資源限制,可以考慮使用集羣並行執行程序。既然單機的資源有限制,那麼就讓程序在多機上運行。比如使用ODPS、Hadoop或者自己搭建服務器集羣,不同的機器處理不同的數據。對於軟件資源限制,可以考慮使用資源池將資源複用。
  4. 在資源限制情況下進行併發編程:如何在資源限制的情況下,讓程序執行得更快呢?方法就是,根據不同的資源限制調整程序的併發度,比如下載文件程序依賴於兩個資源——帶寬和硬盤讀寫速度。有數據庫操作時,涉及數據庫連接數,如果SQL語句執行非常快,而線程的數量比數據庫連接數大很多,則某些線程會被阻塞,等待數據庫連接。

讀書越多越發現自己的無知,Keep Fighting!

本文僅是在自我學習 《Java併發編程的藝術》這本書後進行的筆記和自我總結,有錯歡迎友善指正。

歡迎友善交流,不喜勿噴~
Hope can help~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章