1. 程式人生 > >多執行緒的建立方式和相關面試題

多執行緒的建立方式和相關面試題

在之前的部落格文章中 我們瞭解到了。什麼是多執行緒。如果有沒看的小夥伴可以去看一看

https://blog.csdn.net/zyz0225/article/details/80753214

那麼在本節中,就應該知道如何建立多執行緒。執行緒的建立包括定義執行緒體和建立執行緒物件兩部分。執行緒體是由run()方法定義的。執行時,系統會自動呼叫run()方法來實現執行緒的具體功能的。

Thread類是存放在java.lang類庫中的,但是在編寫程式碼的時候不必刻意去載入java.lang類庫,因為系統會自動載入。run()方法是Thread類裡的一個方法,也是Runnable介面唯一的一個方法。當一執行緒被初始化之後,由

start()方法來呼叫它,一旦run()方法返回,那麼本執行緒也就終止了。子類可以通過繼承Thread類而重寫run()方法,事實上所做的就是覆蓋操作。因此要建立一個多執行緒,必須按照下面的格式來編寫:

public class 類名稱 extends Thread{ // Thread類擴展出子類

public類名稱(){

//編寫子類的構造方法,可省略

}

   屬性 //宣告子類的資料成員

   方法 //定義子類自己的方法

public void  run(){

// 重寫Thread類裡的run()方法

   }

}

用繼承Thread類的子類建立執行緒物件的一般格式為:

子類類名稱 物件名

= new子類類名稱();

例如:SubOfThreadClassname stcn = new SubOfThreadClassname();

啟動該執行緒物件的格式為:子類的物件.start();

例如:stcn.start();

start()的作用是使該執行緒開始執行,Java 虛擬機器呼叫該執行緒的run()方法

【程式碼剖析】應用繼承類Thread的方法實現多執行緒的例子如下:

//檔名:TextDemo.java

class ThreadSubName extends Thread //建立Thread類的子類

private String threadName; //執行緒的名字(變數)

private int Ms; //休眠的毫秒數(變數)

public ThreadSubName(String name, int ms) { //子類的構造方法,為私有變數賦初值

this.threadName = name;

this.Ms = ms;

}

public void run() { //重寫Thread類的run()方法

try {

sleep(Ms);

} catch (InterruptedException e) { //捕獲sleep()的中斷異常

System.out.println("The Thread is Wrong");

}

//打印出當前執行的執行緒的名字和休眠的時間

System.out.println("名字叫" + threadName + "  開始休眠" + Ms + "毫秒");

}

}

public class TextDemo {

public static void main(String[] args) {

ThreadSubName t1 = new ThreadSubName("Thread 1", 200); //建立執行緒物件t1

ThreadSubName t2 = new ThreadSubName("Thread 2", 100); //建立執行緒物件t2

ThreadSubName t3 = new ThreadSubName("Thread 3", 300); //建立執行緒物件t3

t1.start(); //啟動t1執行緒

t2.start(); //啟動t2執行緒

t3.start(); //啟動t3執行緒

}

}

執行結果如下:

名字叫Thread 2  開始休眠100毫秒

名字叫Thread 1  開始休眠200毫秒

名字叫Thread 3  開始休眠300毫秒

【解釋說明】如果拋開執行緒,就用以前學過的知識來預測這段程式碼的運結果,應該是先列印名字叫Thread 1開始休眠200毫秒”,最後打印出“名字叫Thread 3開始休眠300毫秒”。但是從程式碼的執行結果中可以看出,多執行緒並不是順著程式的流程執行的。為什麼會是這樣的結果呢?主要的原因在sleep(Ms);這條語句上。

程式啟動時總是會自動的呼叫main()方法,執行主執行緒,因此main()是建立和啟動執行緒的地方。首先建立了t1t2t3三個執行緒並傳入了相應的引數值,當程式執行到t1.start();啟動執行緒並自動呼叫run()方法,在run()方法中,sleep(200)這個方法使t1執行緒暫時停止執行200毫秒,等200毫秒後,執行緒才可民恢復執行。在t1執行緒休眠的時間裡,把執行權主動的交給了t2.而不是等t1恢復執行後再讓t2執行。所以才會打印出上面的執行結果。

通過前面的學習想必大家都知道,在繼承關係中Java規定一個子類只能有一個父類。但是在編寫程式的過程中,會遇到各種各樣的問題例如在Java中如果一個類繼承了某一個類,同時又想採用多執行緒技

術的時,這時就不能釆用繼承Thread類產生執行緒的方式了,因為Java不允許多繼承,那怎麼辦呢?這時就可以釆用實現Runnable介面來解決這個問題。

Runnable介面來建立執行緒就是在一個類中實現Runnable介面,並在該類中重寫run()方法。建立多執行緒的的一般格式為:

class 類名稱 implements Runnable   // 實現Runnable介面

{

   屬性

   方法

public 類名稱(){//構造方法

}

  public void run(){            // 重寫Runnable接口裡的run()方法

//執行緒的功能程式碼

   }

}

建立執行緒的步驟如下:

1)生成實現Runnable介面的類的例項化物件。程式碼如下:

class RunnableSub implements Runnable

RunnableSub rs = new RunnableSub();

2)用帶有Runnable引數型別的Thread類構造方法建立執行緒物件。程式碼如下:

Thread t = new Threadrs;

3)啟動執行緒。程式碼如下:

t.start();

【程式碼剖析】應用實現Runnable介面的方法建立多執行緒的例子如下:

//檔名:TextDemo_1.java

class RunnableDemo extends ThreadRun implements Runnable {//建立一個Runnable的子類並且這個類也是ThreadRun的子類

Thread t2 = null; //建立全域性變數

public void RDemo(RunnableDemo r1) { //建立一個RDemo方法

Thread t1 = new Thread(r1, "第一執行緒"); //建立執行緒物件t1

System.out.println("正在執行的是" + t1);

t2 = new Thread(r1, "第二執行緒"); //建立執行緒物件t1

System.out.println("建立第二執行緒");

System.out.println("第一執行緒開始休眠");

t2.start(); //啟動t2執行緒

try {

t1.sleep(400); //t1執行緒休眠400毫秒

} catch (InterruptedException e) { //捕獲異常

System.out.println("第一執行緒錯誤");

}

System.out.println("第一執行緒恢復執行");

}

public void run() { //重寫Runnable介面的run()

try {

for (int i = 0; i < 800; i += 100) { //for迴圈設定休眠的時間

System.out.println("第二執行緒的休眠時間:  " + i);

t2.sleep(i);

}

} catch (InterruptedException e) {

System.out.println("第二執行緒錯誤");

}

System.out.println("第二執行緒結束");

}

}

class ThreadRun { // RunnableDemo類的父類

public String print() {

return "我是RunnableDemo的父類";

}

}

public class TextDemo_1 {

public static void main(String[] args) {

RunnableDemo r1 = new RunnableDemo(); //建立RunnableDemo的例項化物件

r1.RDemo(r1); //呼叫RDemo(RunnableDemo r1)方法

System.out.println(r1.print());

}

}

執行結果如下:

正在執行的是Thread[第一執行緒,5,main]

建立第二執行緒

第一執行緒開始休眠

第二執行緒的休眠時間:  0

第二執行緒的休眠時間:  100

第二執行緒的休眠時間:  200

第二執行緒的休眠時間:  300

第一執行緒恢復執行

我是RunnableDemo的父類

第二執行緒的休眠時間:  400

第二執行緒的休眠時間:  500

第二執行緒的休眠時間:  600

第二執行緒的休眠時間:  700

第二執行緒結束

【解釋說明】這段程式碼首先就說明了用Runnable介面建立執行緒是可以解決類多繼承這個難點的。因為RunnableDemo這個類不僅實現了Runnable介面同時也繼承了ThreadRun類。

在建立執行緒物件的時候要注意,RunnableDemo r1 = new RunnableDemo();雖然RunnableDemo是Runnable的子類,但是建立的r1並不是執行緒物件,只是一個普通的類物件。建立真正的執行緒物件是必須用帶有Runnable引數的Thread類建立的物件,例如Thread t1 = new Thread(r1"第一執行緒");t1就是執行緒物件。在Thread類中帶有Runnable介面的構造方法有(可以參考JDKAPI):

q public Thread(Runnable target) 

q public Thread(Runnable target,String name) 

這些構造方法都是分配新的Thread物件的作用其中Runnable target表示run()方法被呼叫的物件ThreadGroup group表示執行緒組String name表示新執行緒的名稱。執行緒的執行流程同繼承Thread類的一樣,可以參考繼承Thread類的程式碼分析。

【考題題幹】建立執行緒有哪兩種方式?它們的區別是什麼?

【參考答案】有兩種實現方法,分別是繼承Thread類與實現Runnable介面。

實現Runnable介面除了擁有和繼承Thread類一樣的功能以外,實現Runnable介面還具有以下功能。

q 適合多個相同程式程式碼的執行緒去處理同一資源的情況,可以把執行緒同程式中的資料有效的分離較好地體現了面向物件的設計思想。

可以避免由於Java的單繼承特性帶來的侷限。例如,class Student已經繼承了class Person,如果要把Student類放入多執行緒中去,那麼就不能使用繼承Thread類的方式。因為在Java中規定了一個類只能有一個父類,不能同時有兩個父類。所以就只能使用實現Runnable介面的方式了。

增強了程式碼的健壯性,程式碼能夠被多個執行緒共同訪問,程式碼與資料是獨立的。多個執行緒可以操作相同的資料,與它們的程式碼無關。當執行緒被構造時,需要的程式碼和資料通過一個物件作為建構函式實參傳遞進去,這個物件就是一個實現了Runnable介面的類的例項。

【考題題幹】關於Threads哪些描述是正確的?

A.執行緒可以建立唯一的子類java.lang.Thread

B.呼叫suspend()方法可以使執行緒終止並且無法再啟動它。

C.程式的執行完畢是以使用者執行緒的結束而標誌結束的,與超級執行緒無關。

D.不同執行緒對相同資料進行訪問時,可能造成資料毀損。

【參考答案】CD