一. 爲什麼要使用併發編程?
併發編程的主要目的是爲了使程序運行的更快;
但是下一個問題出來了,併發編程一定會使我們的程序運行的更快嗎?爲什麼併發編程會使我們的程序變得更快呢?
二. 理解併發編程
2.1併發編程一定會使我們的程序運行的更快嗎?
答案是並不是,併發編程只有在一定的請求量或者計算量的時候纔會顯示出優勢;
2.2 爲什麼有的時候多線程反而會變慢呢?
我們知道os(操作系統)執行併發操作是採用的時間片輪轉算法,簡單的說就是線程1先執行10ms 然後線程B在執行10ms 然後線程A又執行了10ms 線程A執行完畢 線程B又開始執行到結束
我們可以發現這種執行的機制,會導致cpu一會執行A線程 一會執行B線程,這就導出了一個第一個導致多線程慢的因素:線程的上下文切花需要時間
補充:Java創建多線程需要OS的干預,OS需要從用戶態切換到核心態 這個過程很耗費時間
那麼簡單的理解就是:如果多線程執行的速度快於單線程執行的速度,那麼我們可以理解爲多線程一定會在執行前做一些準備,而單線程不需要,所以當任務數或者工作量並不大的時候單線程就會很有優勢;但是當任務數上來後多線程的優勢就體現出來了;
這裏我們有個可以繼續深入研究的點:即具體什麼時候,什麼情況下單線程執行優於多線程
2.3 那麼如何提高併發編程的性能呢?
我們知道併發性能的瓶頸就是頻繁的上下文切換,那麼我們就可以通過減少上下文的切換來提高性能。
減少上下文切換的方法又一下幾種
1. 採用無鎖併發編程:多線程競爭鎖的時候就會引起上下文的切換,所以處理數據的時候應該避免加鎖,可以把數據分段,每個線程來處理一段的數據,
2. CAS算法: Java的Atomic包使用CAS算法來更新數據,不需要加鎖
**3. 儘量合理的配置線程即使用最少的線程:**例如我們處理一個任務10個線程100ms搞定,那麼你開100000個線程來處理,光上下文切換的時間都要超過100ms
**4. 使用協程:**在單線程裏實現任務的調度,並在單線程裏維持多個任務的切換
2.2 爲什麼併發編程會使我們的程序變得更快呢?
首先對於單線程來說,程序是一行一行的執行的,所以假設在120行-180行之間的程序含有數據庫操作需要等待很長時間,那麼180行以後的代碼都必須等到180行的代碼執行完才能繼續執行,而併發編程其實就是異步化,也就是主線程在執行到120行的時候,120-180行的代碼交給另一個線程去執行,主線程可以繼續向下執行,所以兩件事是並行的,所以程序的響應時間就會變快
三.死鎖
死鎖是導致系統功能不可用的重要原因之一;
什麼是死鎖呢?
線程1在等待線程2釋放鎖,線程2也在等線程1釋放另一個鎖,最終導致線程1和2都在等待中,從而不能繼續執行程序;
構成死鎖的必要條件有一下幾個
- 互斥
- 不可剝奪
- 保持申請
- 循環等待
互斥:也就是加互斥鎖,線程1獲取到了對資源1的鎖,那麼線程2就不能獲取對資源1的鎖了
不可剝奪 線程1對資源1加的鎖,線程2不可以撤銷線程1對資源1加的鎖
保持申請 線程1一直持有對資源1的鎖
循環等待 線程1等線程2 線程2等線程1
代碼行 | 線程1的操作 | 線程2 的操作 |
---|---|---|
100行 | 對資源1加鎖 | 其他操作 |
110行 | 執行其他操作 | 對資源2加鎖 |
120行 | 獲取資源2 因爲有鎖所以等待 | 獲取資源1 因爲有鎖所以等待 |
121行 | 因爲線程在等待 所以此行不會執行 | 因爲線程在等待 所以此行不會執行 |
我們可以看到線程1和2都在120行處等待,所以下面的代碼不會執行, 這就是死鎖
3.1 如何避免死鎖呢?
- 避免一個線程同時獲取多個鎖
- 避免一個線程同時佔用多個資源
- 嘗試使用定時鎖來代替內部的鎖機制
- 對於數據庫鎖,加鎖和解鎖必須在同一個連接內,否則解鎖會失敗
四.多線程需要考慮的問題
即使我們使用了多線程,但是系統還是會受到硬盤的讀寫速度,網絡帶寬,cpu處理速度等的影響
所以在設置線程數的時候也要綜合考慮