【多線程】創建線程的三種方式

進程與線程的區別


進程就是一個應用程序在處理機上的一次執行過程,它是一個動態的概念,而線程是進程中的一部分,進程包含多個線程在運行。舉一個在博客中看到的例子。
進程就好比工廠的車間,它代表CPU所能處理的單個任務。任一時刻,CPU總是運行一個進程,其他進程處於非運行狀態。
這裏寫圖片描述

一個車間裏,可以有很多工人。他們協同完成一個任務。
這裏寫圖片描述
線程就好比車間裏的工人。一個進程可以包括多個線程。車間的空間是工人們共享的,比如許多房間是每個工人都可以進出的。這象徵一個進程的內存空間是共享的,每個線程都可以使用這些共享內存。

總結即:
a.聯繫 一個進程可包含多個線程。
b.資源 進程之間資源獨立,同一進程的各線程間共享。某進程內的線程在其它進程不可見。
c.通信:進程間通信IPC,線程間可以直接讀寫進程數據段(如全局變量)來進行通信——需要進程同步和互斥手段的輔助,以保證數據的一致性。
d.調度和切換:線程上下文切換比進程上下文切換要快得多。

創建方式


一:子類覆蓋父類run方法

繼承Thread類的方法儘管是一種多線程實現方式,但Thread本質上也是實現了Runnable接口的一個實例,它代表一個線程的實例,並且,啓動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啓動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並複寫run()方法,就可以啓動新線程並執行自己定義的run()方法。例如:

public class FirstThreadTest extends Thread{  
public static void main(String[] args) {
     Thread thread = new Thread(){
         @Override
         public void run(){
             while(true){
                 try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                 System.out.println(Thread.currentThread().getName());
            }
         }
     };
     thread.start();
    }
}

二:實現Runnable接口

如果自己的類已經extends另一個類,就無法直接extends Thread,此時,必須實現一個Runnable接口。
(1)定義runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。

(2)創建 Runnable實現類的實例,並依此實例作爲Thread的target來創建Thread對象,該Thread對象纔是真正的線程對象。

(3)調用線程對象的start()方法來啓動該線程。
要點如下:

 Thread thread2=new Thread(new Runnable(){
         public void run(){
             while(true){
                 try {
                    Thread.sleep(500);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                 System.out.println(Thread.currentThread().getName());
            }
         }
     });

事實上,當傳入一個Runnable target參數給Thread後,Thread的run()方法就會調用target.run(),參考JDK源代碼:

public void run() {
  if (target != null) {
   target.run();
  }
}

ExecutorService、Callable、Future

實現有返回結果的多線程
(1)創建Callable接口的實現類,並實現call()方法,該call()方法將作爲線程執行體,並且有返回值。

(2)創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。

(3)使用FutureTask對象作爲Thread對象的target創建並啓動新線程。

(4)調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值
放入項目中一個示例:

            ExecutorService executorService = Executors.newCachedThreadPool();
            List<Future<PaperDetail>> resultList = new ArrayList<Future<PaperDetail>>();
            for (PaperDetail returnSinglePaperDetail : returnPaperDetail) {
                //使用ExecutorService執行Callable類型的任務,並將結果保存在future變量中
                Future<PaperDetail> future = executorService.submit(new myCallable(returnSinglePaperDetail));
                //將任務執行結果存儲到List中
                resultList.add(future);
            }
            List<PaperDetail> listPapaerDeatil=new ArrayList<PaperDetail>();

            //遍歷任務的結果
            for (Future<PaperDetail> fp: resultList) {
                try {
                    PaperDetail enPaperDetail =fp.get();
                    listPapaerDeatil.add(enPaperDetail);
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }

                //啓動一次順序關閉,執行以前提交的任務,但不接受新任務。如果已經關閉,則調用沒有其他作用。
                executorService.shutdown();
            }


public class myCallable implements Callable<PaperDetail>{
         public myCallable(PaperDetail paperDetail) {
                this.paperDetail = paperDetail;
            }

          public PaperDetail call(){
        //具體實現略
        }

代碼說明:
上述代碼中Executors類,提供了一系列工廠方法用於創先線程池,返回的線程池都實現了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
創建固定數目線程的線程池。
public static ExecutorService newCachedThreadPool()
創建一個可緩存的線程池,調用execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。
public static ExecutorService newSingleThreadExecutor()
創建一個單線程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
創建一個支持定時及週期性的任務執行的線程池,多數情況下可用來替代Timer類。

ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。如果Executor後臺線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。

for (Future<PaperDetail> fp: resultList)

這一句執行的時候,說明已經有線程執行結束。我們應用其返回結果。

對比:


採用實現Runnable、Callable接口的方式創見多線程時,優勢是:

線程類只是實現了Runnable接口或Callable接口,還可以繼承其他類。但是方式一不能繼承其他類了。

在這種方式下,多個線程可以共享同一個target對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。

附:本文提到的 進程與線程的一個簡單解釋 參考博客如下:
進程與線程的一個簡單解釋

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