并發編程筆記(一):并發編程的挑戰和解決方案

并發系列的文章都是根據閱讀《Java 并發編程的藝術》這本書總結而來,想更深入學習的同學可以自行購買此書進行學習。

并發編程的目的是為了讓程序運行的更快,但是啟動更多的線程不一定會讓程序能夠最大程度的并發執行,甚至有時候,并發比串行還要慢。在使用并發編程的時候,會面臨許多挑戰。

上下文切換

單核處理器也是可以支持多線程執行代碼的。CPU通過分配時間片來實現這個機制。時間片是 CPU 分配給各個線程的時間,因為時間片很短,所以 CPU 會通過不斷切換線程執行,讓我們感覺多個線程是同時執行的。

每次時間片切換之前都會保存上一個任務的狀態,用來方便下次切換回這個任務的時候,可以再次加載這個任務的狀態。這個保存到加載的過程就是一次上下文切換。

這樣的切換是會影響多線程的執行效率的。想象我們看一本英文書籍,如果遇到單詞不認識,我們會去查閱詞典,但是在查閱之前,我們得先記住我們看到那一頁了,以便等到查到單詞后還能繼續在之前看的位置讀下去。雖然這樣能夠保證閱讀的連貫性,但閱讀的速度必然是受到影響的。

多線程并發執行不一定比串行執行快。測試發現,串行和并行做同一件循環操作,在一定達到循環次數之前,并發是沒有串行速度快的。這正是因為線程的創建以及上下文切換有開銷的緣故。

通過一些工具我們可以度量上下文帶來的消耗:

  • 使用 Lmbench3 可以測量上下文切換的時長
  • 使用 vmstat 可以測量上下文切換的次數

那么如何減少上下文的切換呢?

  • 無鎖并發編程。多線程競爭鎖時,會引起上下文切換,所以可以用一線方法來避免使用鎖。例如將數據的 ID 按 Hash 算法取模分段,不同的線程處理不同段的數據。
  • CAS 算法。Java 的 Atomic 包使用 CAS 算法來更新數據,不需要加鎖
  • 使用最少線程:避免創建不必要的線程,如果創建了很多多余的線程,將會造成大量的線程處于等待狀態。
  • 協程:在單線程里實現多任務的調度,并在單線程里維持多個任務間切換。

死鎖

鎖是個非常有用的工具,使用也很簡單易懂,但可能會引起死鎖,從而導致系統不可用。在一些復雜的場景中,可能會遇到死鎖問題,比如線程 t1 拿到鎖之后,因為一些異常情況沒有釋放鎖(例如死循環)。又或者是 t1 拿到了一個數據庫鎖,釋放鎖的時候拋出了異常,沒有釋放掉。

一旦出現死鎖,業務是可以感知的,因為無法繼續提供服務了,我們可以通過 dump 現場來查看哪個線程除了問題,并根據日志信息進行跟蹤代碼。

常見的幾個避免死鎖的方法:

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

資源限制的挑戰

資源限制可以分為計算機硬件資源軟件資源。

硬件資源限制有寬帶速度、硬盤讀寫速度和 CPU 處理速度。如果寬帶速度只有 2Mb/s,某個資源下載速度是 1Mb/s,但系統即使啟動了 10 個線程下載資源,下載速度也不會編程 10Mb/s。

軟件資源限制有數據庫的連接數和 socket 連接等。

因為資源限制的原因,會導致有時候并發執行的任務因為資源不足,甚至還沒有串行執行速度快。比如因為開了多線程導致了一些資源的請求,但資源又不夠用,就會導致一些資源調度和上下文切換的開銷。

對于硬件資源限制,可以使用集群并行執行程序。對于軟件資源,可以考慮使用資源池將資源復用。在資源限制的情況下,要根據不同的資源限制調整程序的并發度。例如有數據庫操作時,設計數據庫連接數,如果 SQL 語句執行的非??欤€程的數量要比數據庫連接數大很多,那么某些線程將會被阻塞,等待數據庫連接。

總結

并發編程有很多挑戰,如果并發程序寫的不嚴謹,出現了問題,定位和解決起來都比較棘手和耗時。所以對于 Java 開發工程師而言,建議多使用 JDK 并發包提供的并發容器和工具類來解決并發問題,這些類已經通過了充分的測試和優化,以上問題都是可以解決的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • layout: posttitle: 《Java并發編程的藝術》筆記categories: Javaexcerpt...
    xiaogmail閱讀 5,859評論 1 19
  • 第1章 并發編程的挑戰 1.1 上下文切換 即便是單核CPU也支持多線程并發,CPU通過給每個線程分配時間片(幾十...
    卑鄙的鹿尤菌閱讀 4,814評論 1 22
  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個最簡單的問題,以這個作為切入點好了 在ma...
    Mr_Baymax閱讀 2,822評論 1 17
  • Java并發編程 來自Java并發編程的藝術個人博客: http://blog.csdn.net/qq_22329...
    越長越圓閱讀 3,254評論 4 54
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,360評論 11 349