多線程

多線程

一些基本概念

進程和線程

  1. 進程:

    正在運行的程序,是系統(tǒng)進行資源分配和調用的獨立單位。

    每一個進程都有它自己的內存空間和系統(tǒng)資源。
  2. 線程:

    是進程中的單個順序控制流,是一條執(zhí)行路徑

    一個進程如果只有一條執(zhí)行路徑,則稱為單線程程序。

    一個進程如果有多條執(zhí)行路徑,則稱為多線程程序。

并行與并發(fā)

前者是邏輯上同時發(fā)生,指在某一個時間內同時運行多個程序。

后者是物理上同時發(fā)生,指在某一個時間點同時運行多個程序。

線程的生命周期及五種基本狀態(tài)

[圖片上傳失敗...(image-f2c2ba-1515143320643)]

新建狀態(tài)(New):當線程對象對創(chuàng)建后,即進入了新建狀態(tài),如:Thread t = new MyThread();
就緒狀態(tài)(Runnable):當調用線程對象的start()方法(t.start();),線程即進入就緒狀態(tài)。處于就緒狀態(tài)的線程,只是說明此線程已經(jīng)做好了準備,隨時等待CPU調度執(zhí)行,并不是說執(zhí)行了t.start()此線程立即就會執(zhí)行;
運行狀態(tài)(Running):當CPU開始調度處于就緒狀態(tài)的線程時,此時線程才得以真正執(zhí)行,即進入到運行狀態(tài)。注:就     緒狀態(tài)是進入到運行狀態(tài)的唯一入口,也就是說,線程要想進入運行狀態(tài)執(zhí)行,首先必須處于就緒狀態(tài)中;
阻塞狀態(tài)(Blocked):處于運行狀態(tài)中的線程由于某種原因,暫時放棄對CPU的使用權,停止執(zhí)行,此時進入阻塞狀態(tài),直到其進入到就緒狀態(tài),才 有機會再次被CPU調用以進入到運行狀態(tài)。根據(jù)阻塞產生的原因不同,阻塞狀態(tài)又可以分為三種:
    1.等待阻塞:運行狀態(tài)中的線程執(zhí)行wait()方法,使本線程進入到等待阻塞狀態(tài);
    2.同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態(tài);
    3.其他阻塞 -- 通過調用線程的sleep()或join()或發(fā)出了I/O請求時,線程會進入到阻塞狀態(tài)。當sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態(tài)。
死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結束生命周期。

實現(xiàn)多線程的三種方式

不能對同一線程對象兩次調用start()方法。

  1. 繼承Thread類,重寫該類的run()方法。

     class MyThread extends Thread {
     
         private int i = 0;
     
         @Override
         public void run() {
             for (i = 0; i < 100; i++) {
                 System.out.println(Thread.currentThread().getName() + " " + i);
             }
         }
     }
     
     class MyThread extends Thread {
    
         private int i = 0;
     
         @Override
         public void run() {
             for (i = 0; i < 100; i++) {
                 System.out.println(Thread.currentThread().getName() + " " + i);
             }
         }
     }
    

繼承Thread類,通過重寫run()方法定義了一個新的線程類MyThread,其中run()方法的方法體代表了線程需要完成的任務,稱之為線程執(zhí)行體。當創(chuàng)建此線程類對象時一個新的線程得以創(chuàng)建,并進入到線程新建狀態(tài)。通過調用線程對象引用的start()方法,使得該線程進入到就緒狀態(tài),此時此線程并不一定會馬上得以執(zhí)行,這取決于CPU調度時機。

  1. 實現(xiàn)Runnable接口,并重寫該接口的run()方法,該run()方法同樣是線程執(zhí)行體,創(chuàng)建Runnable實現(xiàn)類的實例,并以此實例作為Thread類的target來創(chuàng)建Thread對象,該Thread對象才是真正的線程對象

     class MyRunnable implements Runnable {
         private int i = 0;
     
         @Override
         public void run() {
             for (i = 0; i < 100; i++) {
                 System.out.println(Thread.currentThread().getName() + " " + i);
             }
         }
     }
    
     public class ThreadTest {
    
         public static void main(String[] args) {
             Runnable myRunnable = new MyRunnable(); // 創(chuàng)建一個Runnable實現(xiàn)類的對象
             Thread thread1 = new Thread(myRunnable); // 將myRunnable作為Thread target創(chuàng)建新的線程
             Thread thread2 = new Thread(myRunnable);
             thread1.start(); // 調用start()方法使得線程進入就緒狀態(tài)
             thread2.start();
         }
     }
     //-------------------------------------------
     public class test {
         public static void main(String[] args) {
             new Thread(new Runnable() {
                 
                 @Override
                 public void run() {
                     
                 }
             }).start();
             
         }
     }
    

    Thread和Runnable之間到底是什么關系呢?

     public class ThreadTest {
    
         public static void main(String[] args) {
             for (int i = 0; i < 100; i++) {
                 System.out.println(Thread.currentThread().getName() + " " + i);
                 if (i == 30) {
                     Runnable myRunnable = new MyRunnable();
                     Thread thread = new MyThread(myRunnable);
                     thread.start();
                 }
             }
         }
     }
     
     class MyRunnable implements Runnable {
         private int i = 0;
     
         @Override
         public void run() {
             System.out.println("in MyRunnable run");
             for (i = 0; i < 100; i++) {
                 System.out.println(Thread.currentThread().getName() + " " + i);
             }
         }
     }
     
     class MyThread extends Thread {
     
         private int i = 0;
         
         public MyThread(Runnable runnable){
             super(runnable);
         }
     
         @Override
         public void run() {
             System.out.println("in MyThread run");
             for (i = 0; i < 100; i++) {
                 System.out.println(Thread.currentThread().getName() + " " + i);
             }
         }
     }
    

    同樣的,與實現(xiàn)Runnable接口創(chuàng)建線程方式相似,不同的地方在于

     Thread thread = new MyThread(myRunnable);
    

那么這種方式可以順利創(chuàng)建出一個新的線程么?答案是肯定的。至于此時的線程執(zhí)行體到底是MyRunnable接口中的run()方法還是MyThread類中的run()方法呢?通過輸出我們知道線程執(zhí)行體是MyThread類中的run()方法。其實原因很簡單,因為Thread類本身也是實現(xiàn)了Runnable接口,而run()方法最先是在Runnable接口中定義的方法。

public interface Runnable {
   
    public abstract void run();
     
}

我們看一下Thread類中對Runnable接口中run()方法的實現(xiàn):

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

也就是說,當執(zhí)行到Thread類中的run()方法時,會首先判斷target是否存在,存在則執(zhí)行target中的run()方法,也就是實現(xiàn)了Runnable接口并重寫了run()方法的類中的run()方法。但是上述給到的列子中,由于多態(tài)的存在,根本就沒有執(zhí)行到Thread類中的run()方法,而是直接先執(zhí)行了運行時類型即MyThread類中的run()方法。
  1. 使用Callable和Future接口創(chuàng)建線程。具體是創(chuàng)建Callable接口的實現(xiàn)類,并實現(xiàn)clall()方法。并使用FutureTask類來包裝Callable實現(xiàn)類的對象,且以此FutureTask對象作為Thread對象的target來創(chuàng)建線程。

     public class ThreadTest {
    
         public static void main(String[] args) {
     
             Callable<Integer> myCallable = new MyCallable();    // 創(chuàng)建MyCallable對象
             FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask來包裝MyCallable對象
     
             for (int i = 0; i < 100; i++) {
                 System.out.println(Thread.currentThread().getName() + " " + i);
                 if (i == 30) {
                     Thread thread = new Thread(ft);   //FutureTask對象作為Thread對象的target創(chuàng)建新的線程
                     thread.start();                      //線程進入到就緒狀態(tài)
                 }
             }
     
             System.out.println("主線程for循環(huán)執(zhí)行完畢..");
             
             try {
                 int sum = ft.get();            //取得新創(chuàng)建的新線程中的call()方法返回的結果
                 System.out.println("sum = " + sum);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             } catch (ExecutionException e) {
                 e.printStackTrace();
             }
     
         }
     }
     
     
     class MyCallable implements Callable<Integer> {
         private int i = 0;
     
         // 與run()方法不同的是,call()方法具有返回值
         @Override
         public Integer call() {
             int sum = 0;
             for (; i < 100; i++) {
                 System.out.println(Thread.currentThread().getName() + " " + i);
                 sum += i;
             }
             return sum;
         }
     
     }
    

    首先,我們發(fā)現(xiàn),在實現(xiàn)Callable接口中,此時不再是run()方法了,而是call()方法,此call()方法作為線程執(zhí)行體,同時還具有返回值!在創(chuàng)建新的線程時,是通過FutureTask來包裝MyCallable對象,同時作為了Thread對象的target。那么看下FutureTask類的定義:

     public class FutureTask<V> implements RunnableFuture<V> {
    
         //....
    
     }
     
     public interface RunnableFuture<V> extends Runnable, Future<V> {    
    
          void run();
    
     }
    

    于是,我們發(fā)現(xiàn)FutureTask類實際上是同時實現(xiàn)了Runnable和Future接口,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作為Thread對象的target,而Future特性,使得其可以取得新創(chuàng)建線程中的call()方法的返回值。

    執(zhí)行下此程序,我們發(fā)現(xiàn)sum = 4950永遠都是最后輸出的。而“主線程for循環(huán)執(zhí)行完畢..”則很可能是在子線程循環(huán)中間輸出。由CPU的線程調度機制,我們知道,“主線程for循環(huán)執(zhí)行完畢..”的輸出時機是沒有任何問題的,那么為什么sum =4950會永遠最后輸出呢?
    原因在于通過ft.get()方法獲取子線程call()方法的返回值時,當子線程此方法還未執(zhí)行完畢,ft.get()方法會一直阻塞,直到call()方法執(zhí)行完畢才能取到返回值。

三種方式對比

采用實現(xiàn)Runnable、Callable接口的方式創(chuàng)見多線程時,優(yōu)勢是:
線程類只是實現(xiàn)了Runnable接口或Callable接口,還可以繼承其他類。
在這種方式下,多個線程可以共享同一個target對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數(shù)據(jù)分開,形成清晰的模型,較好地體現(xiàn)了面向對象的思想。
劣勢是:
編程稍微復雜,如果要訪問當前線程,則必須使用Thread.currentThread()方法。
使用繼承Thread類的方式創(chuàng)建多線程時優(yōu)勢是:
編寫簡單,如果需要訪問當前線程,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前線程。
劣勢是:
線程類已經(jīng)繼承了Thread類,所以不能再繼承其他父類。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,482評論 1 15
  • Java多線程學習 [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,981評論 1 18
  • 該文章轉自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍閱讀 7,378評論 3 87
  • 我知道單憑相貌去評判一個人的好壞可能不太好 但是我就喜歡美好的東西 換句話說 我是一如既往的膚淺
    負Yi閱讀 501評論 0 1
  • 春天 寫作業(yè)時你又不開心了,情緒的小怪獸在你的小腦袋里搗亂。此刻你沒向以前一樣生悶氣,而是把原因一五一十地說...
    擎天柱_6e9a閱讀 100評論 0 0