1. 程式人生 > >多執行緒的應用場景以及其實現方式

多執行緒的應用場景以及其實現方式

一、執行緒的概要紹
從員工搬貨看多執行緒
現在有一大推貨物堆著,如果我有5個員工搬,肯定會比一個員工搬要快速。但是若是有15個員工搬同一堆貨物,中間肯定會因為空間以及貨物爭搶而產生摩擦,甚至會互相掐架。所以,這就不意味著執行緒越多越好,合理的使用多執行緒,可以充分提升處理器的利用率,提高工作效率

執行緒與程序
程序:每個程序都有獨立的程式碼和資料空間(程序上下文),程序間的切換會有較大的開銷,一個程序包含1–n個執行緒。
執行緒:同一類執行緒共享程式碼和資料空間,每個執行緒有獨立的執行棧和程式計數器(PC),執行緒切換開銷小。
執行緒和程序一樣分為五個階段:建立、就緒、執行、阻塞、終止。
多程序是指作業系統能同時執行多個任務(程式)。
多執行緒是指在同一程式中有多個順序流在執行。
所以執行緒被包含在程序裡,兩者範疇不一樣

多執行緒使用的目的:
1、 吞吐量:做WEB,容器幫你做了多執行緒,但是它只能幫你做請求層面的,簡單的說,就是一個請求一個執行緒(如struts2,是多執行緒的,每個客戶端請求建立一個例項,保證執行緒安全),或多個請求一個執行緒,如果是單執行緒,那隻能是處理一個使用者的請求
2、 伸縮性:通過增加CPU核數來提升效能。
多執行緒的使用場景:
1、 常見的B/S服務(瀏覽器、Web服務)web處理請求(一般web中介軟體會自動管理執行緒),各種專用伺服器(如遊戲伺服器)
2、 FTP下載,多執行緒操作檔案
3、 資料庫用到的多執行緒
4、 分散式計算
5、 tomcat內部採用多執行緒,上百個客戶端訪問同一個WEB應用,tomcat接入後就是把後續的處理扔給一個新的執行緒來處理,這個新的執行緒最後呼叫我們的servlet程式,比如doGet或者dpPost方法
6、 後臺任務:如定時向大量(100W以上)的使用者傳送郵件;定期更新配置檔案、任務排程(如quartz),一些監控用於定期資訊採集
7、 非同步處理:如發微博、記錄日誌
8、費時應用開發,一個費時的計算開個執行緒,前臺加個進度條顯示
9、swing程式設計

二、多執行緒的實現式
常用的實現多執行緒的方有三種,分別是繼承Thread類、實現Runnable介面和使用ExecutorService、Callable、Future實現帶有返回結果的多執行緒。下面一一舉例說明。

繼承Thread類實現多執行緒
繼承Thread類的方法儘管被視為一種多執行緒實現方式,但Thread本質上也是實現了Runnable介面的一個例項,它代表一個執行緒的例項,並且,啟動執行緒的唯一方法就是通過Thread類的start()例項方法。start()方法是一個native方法,它將啟動一個新執行緒,但是執行緒在呼叫start()方法之後並不是立馬就執行,而是將執行緒處於可執行狀態,什麼時候執行是由作業系統決定的,在執行期間執行緒將會執行run()方法裡的內容。

package com.test.thread;

import java.util.HashMap;
import java.util.Map;

public class ThreadDemo extends Thread {

    private String name;
    public ThreadDemo(String name){
        this.name = name;
    }

    @Override
    public void run() {
            Map<String, String> map = new HashMap<>();
            for (int i = 1; i <= 5; i++) {
                map.put("name"+i, name);    //將name+1作為key值,value為傳入的執行緒名字                           
            }
            this.getInfo(map);  //呼叫map的列印方法
    }

    public void getInfo(Map<String, String> map){
        for (String str : map.keySet()) {           
            System.out.println("名為 "+Thread.currentThread().getName()+" value= "+map.get(str));
        }

    }

    public static void main(String[] args) {

        Thread t1 = new ThreadDemo("別鬧");   //定義一個新執行緒
        t1.start();     //啟動執行緒。注意,執行緒在啟動之後並不一定是立馬執行,什麼時候執行取決於作業系統

        Thread t2 = new ThreadDemo("別鬧+1");
        t2.start();

        Thread t3 = new ThreadDemo("別鬧+2");
        t3.start();

    }
}

輸出:
名為 Thread-1 value= 別鬧+1
名為 Thread-0 value= 別鬧
名為 Thread-2 value= 別鬧+2
名為 Thread-0 value= 別鬧
名為 Thread-1 value= 別鬧+1
名為 Thread-0 value= 別鬧
名為 Thread-2 value= 別鬧+2
名為 Thread-0 value= 別鬧
名為 Thread-1 value= 別鬧+1
名為 Thread-0 value= 別鬧
名為 Thread-2 value= 別鬧+2
名為 Thread-1 value= 別鬧+1
名為 Thread-2 value= 別鬧+2
名為 Thread-1 value= 別鬧+1
名為 Thread-2 value= 別鬧+2

再執行一下:

名為 Thread-0 value= 別鬧
名為 Thread-1 value= 別鬧+1
名為 Thread-2 value= 別鬧+2
名為 Thread-2 value= 別鬧+2
名為 Thread-2 value= 別鬧+2
名為 Thread-2 value= 別鬧+2
名為 Thread-2 value= 別鬧+2
名為 Thread-0 value= 別鬧
名為 Thread-0 value= 別鬧
名為 Thread-0 value= 別鬧
名為 Thread-0 value= 別鬧
名為 Thread-1 value= 別鬧+1
名為 Thread-1 value= 別鬧+1
名為 Thread-1 value= 別鬧+1
名為 Thread-1 value= 別鬧+1

說明:
程式啟動執行main時候,java虛擬機器啟動一個程序,主執行緒main在main()呼叫時候被建立。隨著呼叫ThreadDemo 的3個物件的start方法,另外3個執行緒也啟動了,這樣,整個應用就在多執行緒下執行。
由上面的執行結果看出,執行緒的名字若沒有自定義,則會使用預設的名字,格式為“Thread-第幾個執行緒”。執行緒的執行順序是隨機的,一般正常情況下都是執行緒間交叉執行。

實現Runnable介面方式實現多執行緒
我們都知道,一個子類只能繼承一個父類,如果一個類繼承了一個父類,那就不能通過繼承Thread父類來實現多執行緒了,這時,就可以通過實現Runnable介面,來實現多執行緒。

package com.test.thread;

import java.util.HashMap;
import java.util.Map;

public class ThreadDemo implements Runnable {

    private String name;
    public ThreadDemo(String name){
        this.name = name;
    }

    @Override
    public void run() {
            Map<String, String> map = new HashMap<>();
            for (int i = 1; i <= 5; i++) {
                map.put("name"+i, name);    //將name+1作為key值,value為傳入的執行緒名字                           
            }
            this.getInfo(map);  //呼叫map的列印方法
    }

    public void getInfo(Map<String, String> map){
        for (String str : map.keySet()) {           
            System.out.println("名為 "+Thread.currentThread().getName()+" 列印的key= "+str+" value= "+map.get(str));
        }

    }

    public static void main(String[] args) {

        Runnable runnable = new ThreadDemo("無厘頭");
        Thread t1 = new Thread(runnable, "執行緒1");
        t1.start();

        Thread t2 = new Thread(runnable,"執行緒2");
        t2.start();

        Thread t3 = new Thread(runnable,"執行緒3");
        t3.start();

    }
}

輸出:
名為 執行緒3 列印的key= name5 value= 無厘頭
名為 執行緒1 列印的key= name5 value= 無厘頭
名為 執行緒1 列印的key= name3 value= 無厘頭
名為 執行緒1 列印的key= name4 value= 無厘頭
名為 執行緒1 列印的key= name1 value= 無厘頭
名為 執行緒1 列印的key= name2 value= 無厘頭
名為 執行緒2 列印的key= name5 value= 無厘頭
名為 執行緒2 列印的key= name3 value= 無厘頭
名為 執行緒2 列印的key= name4 value= 無厘頭
名為 執行緒3 列印的key= name3 value= 無厘頭
名為 執行緒2 列印的key= name1 value= 無厘頭
名為 執行緒2 列印的key= name2 value= 無厘頭
名為 執行緒3 列印的key= name4 value= 無厘頭
名為 執行緒3 列印的key= name1 value= 無厘頭
名為 執行緒3 列印的key= name2 value= 無厘頭

說明:
ThreadDemo類通過實現Runnable介面,使得該類有了多執行緒類的特徵。run()方法是多執行緒程式的一個約定。所有的多執行緒程式碼都在run方法裡面。在啟動的多執行緒的時候,需要先通過Thread類的構造方法Thread(Runnable target, String name) 構造出物件,然後呼叫Thread物件的start()方法來執行多執行緒程式碼。

使用ExecutorService、Callable、Future實現帶有返回結果的多執行緒
上面所講述的兩種實現多執行緒的方法,都是沒有返回值的,如果想要實現帶返回值的多執行緒,不妨試一下,下面這種方法。

@SuppressWarnings("unchecked")  
public class Test {  
public static void main(String[] args) throws ExecutionException,  
    InterruptedException {  
   System.out.println("----程式開始執行----");  
   Date date1 = new Date();  

   int taskSize = 5;  
   // 建立一個執行緒池  
   ExecutorService pool = Executors.newFixedThreadPool(taskSize);  
   // 建立多個有返回值的任務  
   List<Future> list = new ArrayList<Future>();  
   for (int i = 0; i < taskSize; i++) {  
    Callable c = new MyCallable(i + " ");  
    // 執行任務並獲取Future物件  
    Future f = pool.submit(c);  
    // System.out.println(">>>" + f.get().toString());  
    list.add(f);  
   }  
   // 關閉執行緒池  
   pool.shutdown();  

   // 獲取所有併發任務的執行結果  
   for (Future f : list) {  
    // 從Future物件上獲取任務的返回值,並輸出到控制檯  
    System.out.println(">>>" + f.get().toString());  
   }  

   Date date2 = new Date();  
   System.out.println("----程式結束執行----,程式執行時間【"  
     + (date2.getTime() - date1.getTime()) + "毫秒】");  
}  
}  

class MyCallable implements Callable<Object> {  
private String taskNum;  

MyCallable(String taskNum) {  
   this.taskNum = taskNum;  
}  

public Object call() throws Exception {  
   System.out.println(">>>" + taskNum + "任務啟動");  
   Date dateTmp1 = new Date();  
   Thread.sleep(1000);  
   Date dateTmp2 = new Date();  
   long time = dateTmp2.getTime() - dateTmp1.getTime();  
   System.out.println(">>>" + taskNum + "任務終止");  
   return taskNum + "任務返回執行結果,當前任務時間【" + time + "毫秒】";  
}  

輸出:

說明:
可返回值的任務必須實現Callable介面,類似的,無返回值的任務必須Runnable介面。執行Callable任務後,可以獲取一個Future的物件,在該物件上呼叫get就可以獲取到Callable任務返回的Object了,再結合線程池介面ExecutorService就可以實現傳說中有返回結果的多執行緒了。
ExecutorService、Callable、Future這些物件實際上都是屬於Executor框架中的功能類,推薦大家去看看Executor框架。