摘記自:《java 并發程式設計的藝術》
并發程式設計的目的是為了讓程式運作得更快,但是,并不是啟動更多的線程就能讓程式最大限度地并發執行。在進行并發程式設計時,如果希望通過多線程執行任務讓程式運作得更快,會面臨非常多的挑戰,比如上下文切換的問題、死鎖的問題,以及受限手硬體和軟體的資源限制問題。
上下文切換
單核處理器也支援多線程執行代碼,CPU通過給每個線程配置設定CPU時間片來實作這個機制。時間片是CPU配置設定給各個線程的時間,因為時間片非常短,是以CPU通過不停地切換線程執行。讓我們感覺多個線程是同時執行的,時間片一般是幾十毫秒(ms).
CPU通過時間片配置設定算法來循環執行任務,目前任務執行一個時間片後會切換到下一個任務。但是,在切換前會儲存上一個任 務的狀态,以便下次切換回這個任務時,可以再加載這個任務的狀态。是以任務從儲存到再加載的過程就是一次上下文切換。
這就像我們同時讀兩本書。 當我們在讀 一本英文的技術書時, 發現某個單詞不認識. 于是便打開字典,但是在放下英文技術書之前,大腦必須先記件這本書讀到了哪裡, 等查完單詞之後能夠能續讀這本書.這樣的切換是會影響讀書效率的,同樣上下文切換也會影響多線程的執行速度。
package com;
public class ConcurrencyTest {
private static final long count=100001;
public static void main(String[] args)throws InterruptedException{
concurrency();
serial();
}
private static void concurrency()throws InterruptedException{
long start=System.currentTimeMillis();
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
int a=0;
for(long i=0;i<count;i++){
a+=5;
}
}
});
thread.start();
int b=0;
for(long i=0;i<count;i++){
b--;
}
thread.join();
long time=System.currentTimeMillis()-start;
System.out.println("concurrency:"+time+"ms,b="+b);
}
private static void serial(){
long start=System.currentTimeMillis();
int a=0;
for(long i=0;i<count;i++){
a+=5;
}
int b=0;
for(long i=0;i<count;i++){
b--;
}
long time=System.currentTimeMillis()-start;
System.out.println("serial:"+time+"ms,b="+b+",a="+a);
}
}
測試結果
- 無鎖并發程式設計。多線程競争鎖時,會引起上下文切換,是以多線程處理資料時,可以用一些辦法來避免使用鎖,如将資料的ID按照Hash算法取模分段,不同的線程處理不同段的資料。
- CAS算法。Java的Atomic包使用CAS算法來更新資料,而不需要加鎖。
- 使用最少線程。避免建立不需要的線程,比如任務很少,但是建立了很多線程來處理,這樣會造成大量線程都處于等待狀态。WAITING的線程少了,系統上下文切換的次數就會少,因為每一次從WAITTING到RUNNABLE都會進行一次上下文的切換。
- 協程:在單線程裡實作多任務的排程,并在單線程裡維持多個任務間的切換。