併發Concurrent與並行Parallel的區別

前言

在開始併發與並行工作之前,需要從概念上大概理解下什麼是併發,什麼是並行,以及兩者的區別

單線程程序

一般來說,在沒有線程的幫助下,程序在一個時間段只能執行一段代碼,其它代碼段只有在等待它完成後才能執行。該程序的處理流程從頭到尾只有一條線,這樣的程序我們稱之爲單線程程序(Single Thread Program)

典型的單線程程序:

public class SingleThreadProgram{
  public static void main(String[] args){
    for(int i=0;i<1000;i++){
      System.out.print("SingleThreadProgram");
    }
  }
}

多線程程序

當程序由一個以上的線程所構成時,稱此程序爲多線程程序(Multithread Program),java從設計伊始就把程序的多線程能力列入了考慮範圍。
典型的多線程程序有:
1. GUI應用程序,我們目前做的Swing桌面程序就屬於此類。
2. 較花費時間的I/O處理,一般來說,文件和網絡的輸入/輸出處理比較花費時間,如果在這段無法進行其它處理,則程序性能會大打折扣,遇到這種情況首先要想到用多線程解決問題.
3. 多連接網絡處理。

併發

當有多個線程在操作時,如果系統只有一個CPU,則它根本不可能真正同時進行一個以上的線程,它只能把CPU運行時間劃分成若干個時間段,再將時間段分配給各個線程執行,在一個時間段的線程代碼運行時,其它線程處於掛起狀態.這種方式我們稱之爲併發(Concurrent).

並行

當系統有一個以上CPU時,則線程的操作有可能非併發.當一個CPU執行一個線程時,另一個CPU可以執行另一個線程,兩個線程互不搶佔CPU資源,可以同時進行,這種方式我們稱之爲並行(Parallel)

看圖理解

這裏寫圖片描述

多線程在併發和並行環境中的不同作用

在併發環境時,多線程不可能真正充分利用CPU,節約運行時間,它只是以”掛起->執行->掛起”的方式以很小的時間片分別運行各個線程,給用戶以每個線程都在運行的錯覺.在這種環境中,多線程程序真正改善的是系統的響應性能和程序的友好性.
在並行環境中, 一個時刻允許多個線程運行,這時多線程程序才真正充分利用了多CPU的處理能力, 節省了整體的運行時間.在這種環境中,多線程程序能體現出它的四大優勢:充分利用CPU,節省時間,改善響應和增加程序的友好性.

PS:在多核時代來臨後,開發多線程程序的能力更是每個程序員都該具備的.

創建多線程程序

創建多線程程序我們通常有兩種方法:

讓類繼承java.lang.Thread,這種方法優勢在於調用稍微方便,一般用於後臺批處理程序的場合,但劣勢是類無法再繼承別的類。
讓類實現接口java.lang.Runnable,這種方法調用時需要藉助Thread的幫助,稍顯麻煩,但優勢在於對類繼承體系沒有影響,這是使用線程時最常用的方法。
兩種方法的線程執行部分都在run()函數中,它們的效率沒有差別。

多線程程序創建和啓動示例

創建線程

// 繼承Thread類
public class Thread1 extends Thread{
  public void run(){
    while(true){
      System.out.println("<Thread1 extends Thread>");
    }
  }
}

// 實現Runnable接口
public class Thread2 implements Runnable{
  public void run(){
    while(true){
      System.out.println("<Thread2 implements Runnable>");
    }
  }
}

啓動線程

public class Main{
  public static void main(String[] args){
    // 啓動線程1,Thread1直接繼承自java.lang.Thread類
    Thread1 th1=new Thread1();
    th1.start();

    // 啓動線程2,thread2實現自java.lang.Runnable接口
    Thread2 thread2=new Thread2();
    Thread th2=new Thread(thread2);
    th2.start();

    while(true){
      System.out.println("<Main Thread>");
    }
  }
}

概念解析Start和Run

public void run()

這個函數容納線程啓動後執行的代碼塊,線程啓動起來,run函數中的代碼會得到執行.

Thead.start()

這是啓動一個線程的方法,調用了這個方法後,線程纔會得到執行.

取得線程執行的結果

通過觀察run函數的簽名public void run()我們可以發現,它既沒有輸入參數,也沒有返回值,那如何取得線程的返回值呢?一般來說我們有三種辦法:
1. 讓線程修改公有變量,如某類的靜態公有字段.這種方式古老而危險,最好不要採用.
2. 輪詢線程執行結果,線程執行的結果放在線程類的一個字段中,外界不斷通過輪詢去查看執行結果.這種方式會浪費很多時間,結果也不可靠,不建議採用.
3. 回調方式,把調用方的指針通過線程類的構造函數傳入線程類的一個字段中,當線程執行完取得結果後再通過這個字段反向調用調用方的函數.這是取得線程執行結果的最佳解決方案.

回調方式的實現.

Boss類
這個類用於啓動Secretary線程去查找文件, findFile()是啓動線程並查找的函數, giveBossResult(String file,String reult)是供Secretary類回調的函數.

public class Boss{
  private String name;

  public Boss(String name){
    this.name=name;
  }

  public void giveBossResult(String file,String reult){
    if(reult!=null){
      System.out.println("文件"+file+"序列號等於:"+reult);
    }
    else{
      System.out.println("無法找到文件"+file);
    }
  }

  public void findFile(){   
    Map<String,String> files=new Hashtable<String,String>();    
    files.put("001", "員工花名冊");
    files.put("002", "企業收支");
    files.put("003", "客戶花名錄");
    files.put("004", "對手狀況分析");
    files.put("005", "當月收支");
    files.put("006", "市場份額分析");
    files.put("007", "大連酒店一覽");
    files.put("008", "娛樂場所名錄");
    files.put("009", "關係單位聯繫名錄");

    Secretary andy=new Secretary("Andy",this,"員工花名冊",files);
    Thread th1=new Thread(andy);
    th1.start();

    Secretary cindy=new Secretary("cindy",this,"上市情況分析",files);
    Thread th2=new Thread(cindy);
    th2.start();
  }

  public static void main(String[] args){
    Boss boss=new Boss("Bill");
    boss.findFile();
  }
}

Secretary類

這個類是進行多線程查找文件的類,查找的結果通過回調方法告知Boss實例.
Boss實例,查找的文件名,查找的集合都通過Secretary類的構造函數傳進來.

public class Secretary implements Runnable{
  private String name;
  private Boss boss;
  private String file;
  private Map<String,String> files;

  public Secretary(String name,Boss boss,String file,Map<String,String> files){
    this.name=name;
    this.boss=boss;
    this.file=file;
    this.files=files;
  }

  public void run(){
    for(Map.Entry<String,String> entry:files.entrySet()){
         if(entry.getValue().equals(file)){
           boss.giveBossResult(file,entry.getKey());
           return;
         }
    }

    boss.giveBossResult(file,null);
  }
}

出處:http://www.blogjava.net/junglesong/archive/2008/02/22/181356.html

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