明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

JAVA圖文說明教程 第6講 Java的線程與Java Applet

[摘要]6.1 線程簡介   隨著計算機的飛速發(fā)展,個人計算機上的操作系統(tǒng)也紛紛采用多任務和分時設計,將早期只有大型計算機才具有的系統(tǒng)特性帶到了個人計算機系統(tǒng)中。一般可以在同一時間內執(zhí)行多個程序的操作系統(tǒng)都有進程的概念。一個進程就是一個執(zhí)行中的程序,而每一個進程都有自己獨立的一塊內存空間、一組系統(tǒng)資源。在...
6.1 線程簡介

  隨著計算機的飛速發(fā)展,個人計算機上的操作系統(tǒng)也紛紛采用多任務和分時設計,將早期只有大型計算機才具有的系統(tǒng)特性帶到了個人計算機系統(tǒng)中。一般可以在同一時間內執(zhí)行多個程序的操作系統(tǒng)都有進程的概念。一個進程就是一個執(zhí)行中的程序,而每一個進程都有自己獨立的一塊內存空間、一組系統(tǒng)資源。在進程概念中,每一個進程的內部數據和狀態(tài)都是完全獨立的。Java程序通過流控制來執(zhí)行程序流,程序中單個順序的流控制稱為線程,多線程則指的是在單個程序中可以同時運行多個不同的線程,執(zhí)行不同的任務。多線程意味著一個程序的多行語句可以看上去幾乎在同一時間內同時運行。

  線程與進程相似,是一段完成某個特定功能的代碼,是程序中單個順序的流控制;但與進程不同的是,同類的多個線程是共享一塊內存空間和一組系統(tǒng)資源,而線程本身的數據通常只有微處理器的寄存器數據,以及一個供程序執(zhí)行時使用的堆棧。所以系統(tǒng)在產生一個線程,或者在各個線程之間切換時,負擔要比進程小的多,正因如此,線程被稱為輕負荷進程(light-weight process)。一個進程中可以包含多個線程。

  一個線程是一個程序內部的順序控制流。
  1. 進程:每個進程都有獨立的代碼和數據空間(進程上下文) ,進程切換的開銷大。
  2. 線程:輕量的進程,同一類線程共享代碼和數據空間,每個線程有獨立的運行棧和程序計數器(PC),線程切換的開銷小。
  3. 多進程:在操作系統(tǒng)中,能同時運行多個任務程序。
  4. 多線程:在同一應用程序中,有多個順序流同時執(zhí)行。

  6.1.1 線程的概念模型

  Java內在支持多線程,它的所有類都是在多線程下定義的,Java利用多線程使整個系統(tǒng)成為異步系統(tǒng)。Java中的線程由三部分組成,如圖6.1所示。

  1. 虛擬的CPU,封裝在java.lang.Thread類中。
  2. CPU所執(zhí)行的代碼,傳遞給Thread類。
  3. CPU所處理的數據,傳遞給Thread類。

  圖6.1線程

  6. 1. 2 線程體(1)


  Java的線程是通過java.lang.Thread類來實現的。當我們生成一個Thread類的對象之后,一個新的線程就產生了。

  此線程實例表示Java解釋器中的真正的線程,通過它可以啟動線程、終止線程、線程掛起等,每個線程都是通過類Thread在Java的軟件包Java.lang中定義,它的構造方法為:

   public Thread (ThreadGroup group,Runnable target,String name);

  其中,group 指明該線程所屬的線程組;target實際執(zhí)行線程體的目標對象,它必須實現接口Runnable; name為線程名。Java中的每個線程都有自己的名稱,Java提供了不同Thread類構造器,允許給線程指定名稱。如果name為null時,則Java自動提供唯一的名稱。
  當上述構造方法的某個參數為null時,我們可得到下面的幾個構造方法:

  public Thread ();
  public Thread (Runnable target);
  public Thread (Runnable target,String name);
  public Thread (String name);
  public Thread (ThreadGroup group,Runnable target);
  public Thread (ThreadGroup group,String name);

  一個類聲明實現Runnable接口就可以充當線程體,在接口Runnable中只定義了一個方法 run():
       public void run();

  任何實現接口Runnable的對象都可以作為一個線程的目標對象,類Thread本身也實現了接口Runnable,因此我們可以通過兩種方法實現線程體。

  (一)定義一個線程類,它繼承線程類Thread并重寫其中的方法 run(),這時在初始化這個類的實例時,目標target可為null,表示由這個實例對來執(zhí)行線程體。由于Java只支持單重繼承,用這種方法定義的類不能再繼承其它父類。

 。ǘ┨峁┮粋實現接口Runnable的類作為一個線程的目標對象,在初始化一個Thread類或者Thread子類的線程對象時,把目標對象傳遞給這個線程實例,由該目標對象提供線程體 run()。這時,實現接口Runnable的類仍然可以繼承其它父類。

  每個線程都是通過某個特定Thread對象的方法run( )來完成其操作的,方法run( )稱為線程體。圖6.2表示了java線程的不同狀態(tài)以及狀態(tài)之間轉換所調用的方法。

  圖6.2 線程的狀態(tài)
    
   1. 創(chuàng)建狀態(tài)(new Thread)
  執(zhí)行下列語句時,線程就處于創(chuàng)建狀態(tài):
  Thread myThread = new MyThreadClass( );
  當一個線程處于創(chuàng)建狀態(tài)時,它僅僅是一個空的線程對象,系統(tǒng)不為它分配資源。

  2. 可運行狀態(tài)( Runnable )
  Thread myThread = new MyThreadClass( );
  myThread.start( );
  當一個線程處于可運行狀態(tài)時,系統(tǒng)為這個線程分配了它需的系統(tǒng)資源,安排其運行并調用線程運行方法,這樣就使得該線程處于可運行( Runnable )狀態(tài)。需要注意的是這一狀態(tài)并不是運行中狀態(tài)(Running ),因為線程也許實際上并未真正運行。由于很多計算機都是單處理器的,所以要在同一時刻運行所有的處于可運行狀態(tài)的線程是不可能的,Java的運行系統(tǒng)必須實現調度來保證這些線程共享處理器。
  
  3. 不可運行狀態(tài)(Not Runnable)
  進入不可運行狀態(tài)的原因有如下幾條:
  1) 調用了sleep()方法;
  2) 調用了suspend()方法;
  3) 為等候一個條件變量,線程調用wait()方法;
  4) 輸入輸出流中發(fā)生線程阻塞;
  不可運行狀態(tài)也稱為阻塞狀態(tài)(Blocked)。因為某種原因(輸入/輸出、等待消息或其它阻塞情況),系統(tǒng)不能執(zhí)行線程的狀態(tài)。這時即使處理器空閑,也不能執(zhí)行該線程。

  4. 死亡狀態(tài)(Dead)
  線程的終止一般可通過兩種方法實現:自然撤消(線程執(zhí)行完)或是被停止(調用stop()方法)。目前不推薦通過調用stop()來終止線程的執(zhí)行,而是讓線程執(zhí)行完。

  

  6. 1. 2 線程體(2)


  ◇線程體的構造

  任何實現接口Runnable的對象都可以作為一個線程的目標對象,上面已講過構造線程體有兩種方法,下面通過實例來說明如何構造線程體的。

  例6.1 通過繼承類Thread構造線程體
  class SimpleThread extends Thread {
  public SimpleThread(String str) {
   super(str); //調用其父類的構造方法
  }
  public void run() { //重寫run方法
   for (int i = 0; i < 10;="" i++)="" {="">
    System.out.println(i + " " + getName());
             //打印次數和線程的名字
    try {
      sleep((int)(Math.random() * 1000));
             //線程睡眠,把控制權交出去
    } catch (InterruptedException e) {}
  }

     System.out.println("DONE! " + getName());
             //線程執(zhí)行結束
    }
  }
  public class TwoThreadsTest {
   public static void main (String args[]) {
    new SimpleThread("First").start();
             //第一個線程的名字為First
    new SimpleThread("Second").start();
             //第二個線程的名字為Second
  }
  }

   運行結果:
    0 First
    0 Second
    1 Second
    1 First
    2 First
    2 Second
    3 Second
    3 First
    4 First
    4 Second
    5 First
    5 Second
    6 Second
    6 First
    7 First
    7 Second
    8 Second
    9 Second
    8 First
    DONE! Second
    9 First
    DONE! First

  仔細分析一下運行結果,會發(fā)現兩個線程是交錯運行的,感覺就象是兩個線程在同時運行。但是實際上一臺計算機通常就只有一個CPU,在某個時刻只能是只有一個線程在運行,而java語言在設計時就充分考慮到線程的并發(fā)調度執(zhí)行。對于程序員來說,在編程時要注意給每個線程執(zhí)行的時間和機會,主要是通過讓線程睡眠的辦法(調用sleep()方法)來讓當前線程暫停執(zhí)行,然后由其它線程來爭奪執(zhí)行的機會。如果上面的程序中沒有用到sleep()方法,則就是第一個線程先執(zhí)行完畢,然后第二個線程再執(zhí)行完畢。所以用活sleep()方法是學習線程的一個關鍵。


  例6.2 通過接口構造線程體
   public class Clock extends java.applet.Applet implements Runnable {//實現接口
      Thread clockThread;
      public void start() {
         //該方法是Applet的方法,不是線程的方法
      if (clockThread == null) {
         clockThread = new Thread(this, "Clock");
         /*線程體是Clock對象本身,線程名字為"Clock"*/
         clockThread.start(); //啟動線程
         }
      }

      public void run() { //run()方法中是線程執(zhí)行的內容
         while (clockThread != null) {
         repaint(); //刷新顯示畫面
         try {
           clockThread.sleep(1000);
           //睡眠1秒,即每隔1秒執(zhí)行一次
          } catch (InterruptedException e){}
         }
      }

      public void paint(Graphics g) {
          Date now = new Date(); //獲得當前的時間對象
          g.drawString(now.getHours() + ":" + now.getMinutes()+ ":" +now.getSeconds(), 5, 10);//顯示當前時間
      }

      public void stop() {
        //該方法是Applet的方法,不是線程的方法
          clockThread.stop();
          clockThread = null;
      }
   }

  上面這個例子是通過每隔1秒種就執(zhí)行線程的刷新畫面功能,顯示當前的時間;看起來的效果就是一個時鐘,每隔1秒就變化一次。由于采用的是實現接口Runnable的方式,所以該類Clock還繼承了Applet, Clock就可以Applet的方式運行。