執行緒的基本解析

wudidamowang666發表於2019-07-04

執行緒物件是可以產生執行緒的物件。比如在Java平臺中Thread物件,Runnable物件。執行緒,是指正在執行的一個指點令序列。在java平臺上是指從一個執行緒物件的start()開始,執行run方法體中的那一段相對獨立的過程。相比於多程式,多執行緒的優勢有:

    (1)程式之間不能共享資料,執行緒可以;

    (2)系統建立程式需要為該程式重新分配系統資源,故建立執行緒代價比較小;

    (3)Java語言內建了多執行緒功能支援,簡化了java多執行緒程式設計。

一、建立執行緒和啟動

  (1)繼承Thread類建立執行緒類

通過繼承Thread類建立執行緒類的具體步驟和具體程式碼如下:

   • 定義一個繼承Thread類的子類,並重寫該類的run()方法;

   • 建立Thread子類的例項,即建立了執行緒物件;

   • 呼叫該執行緒物件的start()方法啟動執行緒。

class SomeThead extends Thraad   { 
    public void run()   { 
     //所要重寫的方法
    }  
 } 
 
public static void main(String[] args){
 SomeThread oneThread = new SomeThread();   
  步驟3:啟動執行緒:   
 oneThread.start(); 
}

 

(2)實現Runnable介面建立執行緒類

通過實現Runnable介面建立執行緒類的具體步驟和具體程式碼如下:

   • 定義Runnable介面的實現類,並重寫該介面的run()方法;

   • 建立Runnable實現類的例項,並以此例項作為Thread的target物件,即該Thread物件才是真正的執行緒物件。

class SomeRunnable implements Runnable   { 
  public void run()   { 
  //所要重寫的方法 
  }  
} 
Runnable oneRunnable = new SomeRunnable();   
Thread oneThread = new Thread(oneRunnable);   
oneThread.start();

以上的兩種方法均可實現建立執行緒類

然而,本魔王一開始乍一看的時候感覺大體上好像並沒有什麼區別,但事實顯然並沒有這麼簡單,試問一下,當使用Thread時候如有需求要求繼承其他類的時候,是可以用extend繼承父類的域,那萬一要繼承的不只是一個父類呢,Thread畢竟只是一個類,不是介面,誒!!對了,這時候用Runnable就會方便很多,下面為大家總結的是Runnable與Thread的相關性。

 

****Runnable和Thread的區別和聯絡?****

1) Thread是一個類,Runnable是一個介面;

2) Thread類實現了Runnable介面,重寫了run方法.

3) Runnable是一個介面,定義一個類實現介面的同時還可以繼承其他的類 ; Runnable 支援多繼承的寫法;

4) Runable可以簡單的實現資料的共享 ;Thread不太好實現;其實都可以實現

5) Runnable適合多個相同的程式程式碼的執行緒去處理同一個資源,避免Java中的單繼承的限制,增加程式的健壯性,程式碼可以被多個執行緒共享,程式碼和資料獨立。執行緒池只能放入實現Runnable 類執行緒,不能直接放入繼承Thread的類.

 

在面試的時候,很多面試官都喜歡問一些關於執行緒狀態的問題,可能不多問的特別細,但起碼道理我們得說的出來,下圖是執行緒的生命週期,就挑幾個常見的給大家解釋的,其實有一些是我自己歸納的,也有一些是網上dd下來的,懂的是什麼道理,自己整理一套白話最好。

  • 新執行緒:
    •  用new關鍵字和Thread類或其子類建立一個執行緒物件後,該執行緒物件就處於新生狀態。處於新生狀態的執行緒有自己的記憶體空間,通過呼叫start方法進入就緒狀態(runnable),它僅僅作為一個物件例項存在, JVM沒有為其分配CPU時間片和其他執行緒執行資源;。

      注意:不能對已經啟動的執行緒再次呼叫start()方法,否則會出現Java.lang.IllegalThreadStateException異常。

       

  • 就緒狀態:
    • 在處於建立狀態的執行緒中呼叫start方法將執行緒的狀態轉換為就緒狀態(儘管是採用佇列形式,事實上,把它稱為可執行池而不是可執行佇列。因為cpu的排程不一定是按照先進先出的順序來排程的)。等待系統為其分配CPU。等待狀態並不是執行狀態,當系統選定一個等待執行的Thread物件後,它就會從等待執行狀態進入執行狀態,系統挑選的動作稱之為“cpu排程”。一旦獲得CPU,執行緒就進入執行狀態並自動呼叫自己的run方法
    • 提示:如果希望子執行緒呼叫start()方法後立即執行,可以使用Thread.sleep()方式使主執行緒睡眠一夥兒,轉去執行子執行緒。(sleep()方法在下面會解釋)

       

  • 執行狀態:
    • 處於執行狀態的執行緒是最複雜的,有阻塞狀態,就緒狀態和死亡狀態
    • 處於就緒狀態的執行緒,如果獲得了cpu的排程,就會從就緒狀態變為執行狀態,執行run()方法中的任務。如果該執行緒失去了cpu資源,就會又從執行狀態變為就緒狀態。重新等待系統分配資源。也可以對在執行狀態的執行緒呼叫yield()方法,它就會讓出cpu資源,再次變為就緒狀態。(**當某個執行緒呼叫了yield()方法暫停之後,優先順序與當前執行緒相同,或者優先順序比當前執行緒更高的就緒狀態的執行緒更有可能獲得執行的機會,當然,只是有可能,因為我們不可能精確的干涉cpu排程執行緒。)
  • 等待/阻塞:
    • 阻塞狀態在這裡我不便多說,因為我自己都還沒看透(噴血),我只能理解的是當一個執行緒佔用CPU執行時被其他執行緒或者語句剝奪或者說暫停了其了使用權,從而進入阻塞狀態。
    • 在阻塞狀態的執行緒不能進入就緒佇列。只有當引起阻塞的原因消除時,如睡眠時間已到,或等待的I/O裝置空閒下來,執行緒便轉入就緒狀態,重新到就緒佇列中排隊等待,被系統選中後從原來停止的位置開始繼續執行。

 

 

 

Java提供了一些便捷的方法用於執行緒狀態的控制,就舉幾個常用的例子,多了我也不會。具體如下:

1、執行緒睡眠——sleep

      如果我們需要讓當前正在執行的執行緒暫停一段時間,並進入阻塞狀態,則可以通過呼叫Thread的sleep方法。

注:

   (1)sleep是靜態方法,最好不要用Thread的例項物件呼叫它,因為它睡眠的始終是當前正在執行的執行緒,而不是呼叫它的執行緒物件,它只對正在執行狀態的執行緒物件有效。如下面的例子:

 1 package day7_3HomeWork;
 2 
 3 public class Thread_text {
 4 
 5     public static void main(String[] args) {
 6         Thread_input ti = new Thread_input();
 7         ti.start();
 8 
 9     }
10 }
11 class Thread_input extends Thread{
12     String[] str = {"我","我愛","我愛福","我愛福建","我愛福建工","我愛福建工程","我愛福建工程學","我愛福建工程學院"};
13     @Override
14     public void run() {
15         // TODO Auto-generated method stub
16         for(int i=0;i<str.length;i++)
17         {
18             System.out.println(str[i]);
19             try {
20                 sleep(2000);
21             } catch (InterruptedException e) {
22                 // TODO Auto-generated catch block
23                 e.printStackTrace();
24             }
25         }    
26     }
27 }

相當於只是暫停當前的程式執行,執行緒處於阻塞狀態,在2000mills的睡眠時間結束的時候才能重新變回就緒狀態,而就緒狀態進入到執行狀態,是由系統控制的,我們不可能精準的去幹涉它,所以如果呼叫Thread.sleep(1000)使得執行緒睡眠1秒,可能結果會大於1秒。下圖是sleep的方法,需要傳入的是一個long mills的引數,這是毫秒的單位。

(2)Java執行緒排程是Java多執行緒的核心,只有良好的排程,才能充分發揮系統的效能,提高程式的執行效率。但是不管程式設計師怎麼編寫排程,只能最大限度的影響執行緒執行的次序,而不能做到精準控制。

 

相關文章