多執行緒的建立方式和相關面試題
在之前的部落格文章中 我們瞭解到了。什麼是多執行緒。如果有沒看的小夥伴可以去看一看
https://blog.csdn.net/zyz0225/article/details/80753214
那麼在本節中,就應該知道如何建立多執行緒。執行緒的建立包括定義執行緒體和建立執行緒物件兩部分。執行緒體是由run()方法定義的。執行時,系統會自動呼叫run()方法來實現執行緒的具體功能的。
Thread類是存放在java.lang類庫中的,但是在編寫程式碼的時候不必刻意去載入java.lang類庫,因為系統會自動載入。run()方法是Thread類裡的一個方法,也是Runnable介面唯一的一個方法。當一執行緒被初始化之後,由
public class 類名稱 extends Thread{ // 從Thread類擴展出子類
public類名稱(){
//編寫子類的構造方法,可省略
}
屬性 //宣告子類的資料成員
方法 //定義子類自己的方法
public void run(){
// 重寫Thread類裡的run()方法
}
}
用繼承Thread類的子類建立執行緒物件的一般格式為:
子類類名稱 物件名
例如: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()是建立和啟動執行緒的地方。首先建立了t1,t2,t3三個執行緒並傳入了相應的引數值,當程式執行到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 Thread(rs);
(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介面的構造方法有(可以參考JDK.API):
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 適合多個相同程式程式碼的執行緒去處理同一資源的情況,可以把執行緒同程式中的資料有效的分離,較好地體現了面向物件的設計思想。
q 可以避免由於Java的單繼承特性帶來的侷限。例如,class Student已經繼承了class Person,如果要把Student類放入多執行緒中去,那麼就不能使用繼承Thread類的方式。因為在Java中規定了一個類只能有一個父類,不能同時有兩個父類。所以就只能使用實現Runnable介面的方式了。
q 增強了程式碼的健壯性,程式碼能夠被多個執行緒共同訪問,程式碼與資料是獨立的。多個執行緒可以操作相同的資料,與它們的程式碼無關。當執行緒被構造時,需要的程式碼和資料通過一個物件作為建構函式實參傳遞進去,這個物件就是一個實現了Runnable介面的類的例項。
【考題題幹】關於Threads哪些描述是正確的?
A.執行緒可以建立唯一的子類java.lang.Thread。
B.呼叫suspend()方法可以使執行緒終止並且無法再啟動它。
C.程式的執行完畢是以使用者執行緒的結束而標誌結束的,與超級執行緒無關。
D.不同執行緒對相同資料進行訪問時,可能造成資料毀損。
【參考答案】CD