Java多執行緒_建立執行緒的多種方式
title: Java多執行緒_建立執行緒的多種方式
date: 2022-02-28 19:02:15
tags: Java
Java多執行緒_建立執行緒的多種方式
一、執行緒與程序
幾乎所有的作業系統都支援程序的概念,所有執行中的任務都對應一個程序(Process)。當一個程式進入記憶體執行時,即變成一個程序。程序是處於執行中的程式,並且具有一定的獨立功能,程序是系統進行資源排程的一個獨立單位。
一般而言,程序包含三個特徵:獨立性、動態性、併發性。
一個程式執行後至少會有一個程序,一個程序可以包含多個執行緒,但至少要有個執行緒。
歸納起來就是說:作業系統可以同時執行多個任務,每個任務就是程序;程序可以同時執行多個任務,每個任務就是執行緒。
溫馨提示(併發性與並行性):
併發性(concurrency)和並行性(parallel)是兩個概念,並行是指在同一時刻,有多條指令在多個處理器上同時執行;併發是指在同一時刻只能有一條指令執行,但多個程序指令被快速輪換執行,使得在巨集觀上具有多個程序被同時執行的效果。
二、多執行緒的優勢
執行緒在程式中是獨立的、併發的執行流,與分隔的程序相比,執行緒分隔程度要小很多。當作業系統建立一個程序時,必須為該程序分配獨立的記憶體空間,並分配大量的相關資源;但建立執行緒則簡單很多,因此使用多執行緒來實現併發比使用多程序實現併發要強很多。
1、程序之間不能共享記憶體,但執行緒之間共享記憶體非常容易,
2、系統建立程序時需要為該程序重新分配系統資源,但建立執行緒則代價要小很多,因此使用多執行緒實現多工併發要比程序效率高很多。
3、Java語言內建了多執行緒功能支援,而不是單純的作為底層作業系統的排程模式,從而簡化了Java的多執行緒程式設計。
三、執行緒的建立和啟動
Java使用Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的例項,以下則介紹其中三種常見建立執行緒物件的方法。
1、繼承Thread類建立執行緒類
建立步驟:
①定義Thread類的子類,並重寫該類的run()方法,該run()方法體就代表了執行緒需要完成的任務。因此吧run()方法稱為執行緒執行體。
②建立Thread子類的例項,即建立了執行緒物件。
③呼叫執行緒物件的start()方法來啟動該執行緒。
/**
* @program: code
* @description: 通過繼承Thread類建立執行緒物件
* @author: yangzhihong
* @create: 2022-02-28 20:29
**/
public class Thread_jc extends Thread {
private int i;
//重寫run()方法,run()方法的方法體就是執行緒執行體。
public void run(){
for (i = 0; i < 100; i++) {
//d當執行緒類繼承Thread類時,直接使用this即可獲取當前執行緒
//Thread物件的getName()返回當前執行緒的名字
//因此可以直接呼叫getName()方法返回當前執行緒的名字
System.out.println("我是子執行緒" + getName() + "" + i);
}
}
public static void main(String[] args){
for (int i = 0; i < 100; i++) {
//呼叫Thread的currentThread()方法獲取當前執行緒
System.out.println("我是主執行緒" + Thread.currentThread().getName() + "" + i);
if (i == 20) {
//建立並啟動第一個執行緒
new Thread_jc().start();
//建立並啟動第二個執行緒
new Thread_jc().start();
}
}
}
}
Thread類提供了常用的API
Thread.currentThread() : 靜態方法用於獲取當前的執行緒物件。
getName() : 可以獲取執行緒物件的名稱。
主執行緒的名稱預設是main,不能修改。
子執行緒的名稱預設是 Thread-0,.......
可以修改,修改方式,可以提供有參構造方法,呼叫父類的有參構造方法Thread(String name)
也可以使用setName(String name) 設定執行緒名稱。
2、實現Runnable介面建立執行緒
自定義一個類實現Runnable介面,這個類是一個任務類,這個任務類需要重寫Runnable介面中的任務方法,這個類的物件是一個任務物件,如果這個任務物件的任務需要在子執行緒中執行,必須依賴執行緒物件。
建立步驟:
①定義Runnable介面的實現類,並重寫該方法的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體。
②建立Runnable實現類的例項,並以此作為Thread的target來建立Thread物件,該Thread物件才是真正的執行緒物件。
③呼叫執行緒執行緒物件的start()方法來啟動執行緒。
**注意:Runnable物件僅僅作為Thread物件的target,Runnable實現類裡包含的run()方法僅作為執行緒執行體。而實際的執行緒物件依然是Thread例項,只是Thread執行緒負責執行其target的run()方法。4
/**
* @program: code
* @description: 實現Runnable介面建立執行緒物件
* @author: yangzhihong
* @create: 2022-03-01 10:24
**/
// 自定義一個任務類
class MyTask implements Runnable {
@Override
public void run() {
for(int i=1; i<=100; i++) {
System.out.println(Thread.currentThread().getName() + "----" + "刷出第" + i + "個小怪");
}
}
}
public class RunnableThread {
public static void main(String[] args) {
// 建立任務物件
MyTask myTask = new MyTask();
// 建立執行緒物件
Thread thread = new Thread(myTask, "子執行緒");
// 開啟子執行緒物件的任務
thread.start();
for(int j=1; j<=100; j++) {
System.out.println(Thread.currentThread().getName() + "----" + "刷出第" + j + "個大BOSS");
}
}
}
3、使用Callable和Future建立執行緒
從Java5 開始,Java提供了Callable介面,該介面與Runnable介面非常相似,Callable介面提供了一個call()方法可以作為執行緒執行體,但call()方法比run()方法功能更強大:可以有返回值、可以宣告丟擲異常。
Java5 提供了一個與Callable配套的Future介面,Future有個實現類FutureTask,該類的RunnableFuture介面對Future、Runnable都進行了實現。可以作為中介來關聯Runnable與Future,並通過Future的公共方法V get() 獲取到Callable的返回值,於是乎Callable物件就可以作為Thread的target來建立執行緒了。(V get()方法中的V是表示泛型,表示Callable接口裡的型別必須與call()返回值型別相同)
建立步驟:
①建立Callable介面的實現類,並實現call()方法,該call()方法將作為執行緒執行體,並且該call()方法有返回值,再建立Callable實現類的例項物件。從Java8 開始可以直接使用Lambda表示式建立Callable物件。
②使用FutureTask類包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的返回值。
③使用FutureTask物件作為Thread物件的target建立並啟動新執行緒。
④呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @program: code
* @description: 通過Callable與Future來建立執行緒物件
* @author: yangzhihong
* @create: 2022-03-01 11:56
**/
// 實現了Callable介面的類,也是一個任務類
class MyCall implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i=1; i<=100; i++) {
sum += i;
}
return sum;
}
}
public class CallableFutureThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 建立任務物件
MyCall myCall = new MyCall();
// 建立FutureTask物件這個物件實現了RunnableFuture介面
// RunnableFuture多繼承了Runnable介面和Future介面
// 在構造FutureTask物件時,可以通過構造方法將Callable介面的
// 實現物件傳入
FutureTask<Integer> futureTask = new FutureTask<>(myCall);
// 建立一個執行緒物件
Thread thread = new Thread(futureTask);
thread.start();
// 通過get()方法來獲取Callable任務方法的返回結果
if(futureTask.isDone()) {
Integer result = futureTask.get();
System.out.println(result);
}
}
}
4、這三種建立執行緒方式的對比
通過繼承Thread類和實現Runnable、Callable介面都可以實現多執行緒,不過實現Runnable介面與實現Callable介面的方式基本相同,只是Callable接口裡定義的方法有返回值和可以宣告丟擲異常,因此可以將Runnable和Callable歸為同一種實現介面方式。
實現介面方式建立執行緒優缺點:
①執行緒類只是實現了Runnable介面或Callable介面,還可以繼承其他類。
②在這種方式下,多個執行緒可以共享同一個target物件,所以非常適合多個相同執行緒來處理同一份資源,從而可以將CPU、程式碼和資料分開。形成清晰的模型。較好的體現了面向物件的思想。
③缺點,程式設計稍微複雜一些,如果需要訪問當前執行緒,則必須使用Thread.currentThread()方法。
採用繼承Thread類方式建立執行緒優缺點:
①缺點,因為執行緒類已經繼承了Thread類,所以不能再繼承其他父類。
②優點,編寫簡單,如果需要訪問當前執行緒,則不需要使用Thread.currentThread()方法,直接this即可獲得當前執行緒。
因此一般推薦採用實現Runnable介面、Callable介面方式來建立多執行緒。