線程的創建和啓動
Java使用Thread類代表線程,所有線程對象都必須是Thread類或其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程序流(一段順序執行的的代碼)。Java使用線程執行體來代表這段流程。
創建線程有三個方法:
- 方法1.繼承Thread類創建線程類
- 方法2.實現Runnable接口創建線程類
- 方法3.使用Callable和Future創建線程
方法1
package com.thread.create_and_start;
/**
* 繼承Thread類創建多線程
* 創建並啓動多線程步驟:
* 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務。因此把run()方法稱爲線程執行體
* 創建Thread子類的實例,即創建了線程對象
* 調用線程對象的start()方法來啓動該線程
* @author tengyu
*
*/
public class FirstThread extends Thread{
private int i;
//重寫run()方法,run()方法的方法體就是線程執行體
public void run(){
for(; i < 10; i++){
//當線程類繼承Thread類時,直接使用this即可獲取當前線程
//Thread對象的getName()返回當前線程的名字
//因此可以直接調用getName()方法返回當前線程的名字
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args){
for(int i = 0; i < 10; i++){
//調用Thread的currentThread()方法獲取當前線程
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 2){
//創建並啓動第一個線程
new FirstThread().start();
//創建並啓動第二個線程
new FirstThread().start();
}
}
}
}
得到結果:
main 0
main 1
main 2
Thread-0 0
Thread-0 1
main 3
Thread-0 2
Thread-1 0
main 4
main 5
main 6
Thread-1 1
Thread-0 3
Thread-1 2
main 7
Thread-1 3
Thread-0 4
Thread-0 5
Thread-0 6
Thread-0 7
Thread-0 8
Thread-0 9
Thread-1 4
Thread-1 5
main 8
main 9
Thread-1 6
Thread-1 7
Thread-1 8
Thread-1 9
當循環變量i=2時,創建並啓動了兩個新線程。並且兩個線程的循環變量不連續表明它們沒有共享數據。
上面程序用到的方法:
Thread.currentThread():currentThread()是Thread類的靜態方法,該 方法總是返回當前正在執行的線程對象。
getName():該方法是Thread類的實例方法,該方法返回調用該方法的線程名字。
方法2
package com.thread.create_and_start;
/**
* Runnable接口創建線程類
* 步驟:
* 定義Runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體
* 創建Runnable實現類的實例,並以此實例作爲Thread的target來創建Thread對象,該Thread對象纔是真正的線程對象
* 調用線程對象的start()方法來啓動線程
* @author tengyu
*
*/
//通過實現Runnable接口來創建線程類
public class SecondThread implements Runnable{
private int i;
public static void main(String[] args) {
// TODO 自動生成的方法存根
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if( i == 2){
SecondThread st = new SecondThread();
//通過new Thread(target , name)方法創建新線程
new Thread(st , "新線程1").start();
new Thread(st , "新線程2").start();
}
}
}
@Override
//接口需要實現的方法,線程執行體
public void run() {
// TODO 自動生成的方法存根
for( ; i < 10; i++){
//當線程類實現Runnable接口時
//如果想獲取當前線程,只能用Thread.currentThread().getName()
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
運行程序:
main 0
main 1
main 2
新線程1 0
新線程1 1
新線程1 2
新線程1 3
新線程1 4
新線程1 5
新線程1 6
main 3
新線程2 7
新線程1 7
新線程2 8
main 4
新線程1 9
main 5
main 6
main 7
main 8
main 9
線程1,2的i變量是連續的,說明採用該方法創建的多個線程可以共享線程類的實例變量。
方法3
package com.thread.create_and_start;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 使用Callable和Future創建線程
* 創建並啓動有返回值的線程的步驟
* 創建Callable接口的實現類,並實現call()方法,該call()方法將作爲線程執行體,且該call()方法有返回值,再創建
* Callable實現類的實例
* 使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值
* 使用FutureTask對象作爲Thread對象的target創建並啓動線程
* 調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值
* @author tengyu
*
*/
public class ThirdThread {
public static void main(String[] args){
//創建Callable對象
ThirdThread rt = new ThirdThread();
//先使用Lambda表達式創建Callable<Integer>對象
//使用FutureTask來包裝Callable對象
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
int i = 0;
for(; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " 的循環變量i的值:"
+ i);
}
//call()方法可以有返回值
return i;
});
for(int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " 的循環變量i的值:"
+ i);
if(i == 2){
//實質是以Callable對象來創建並啓動線程
new Thread(task, "有返回值的線程").start();
}
}
try{
//獲取線程返回值
System.out.println("線程返回值:" + task.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
運行結果:
main 的循環變量i的值:0
main 的循環變量i的值:1
main 的循環變量i的值:2
main 的循環變量i的值:3
main 的循環變量i的值:4
main 的循環變量i的值:5
main 的循環變量i的值:6
main 的循環變量i的值:7
main 的循環變量i的值:8
main 的循環變量i的值:9
有返回值的線程 的循環變量i的值:0
有返回值的線程 的循環變量i的值:1
有返回值的線程 的循環變量i的值:2
有返回值的線程 的循環變量i的值:3
有返回值的線程 的循環變量i的值:4
有返回值的線程 的循環變量i的值:5
有返回值的線程 的循環變量i的值:6
有返回值的線程 的循環變量i的值:7
有返回值的線程 的循環變量i的值:8
有返回值的線程 的循環變量i的值:9
線程返回值:10
Callable接口可以看做是Runnable接口的加強版,Callable接口提供了一個call()方法可以作爲線程執行體,但是call()方法比run()方法更強大。強大之處在於call()方法可以有返回值,並且可以拋出異常。
call()方法並不能直接調用,所以使用Future接口來代表返回值。
三種方法的優缺點
通過繼承Thread類或實現Runnable、Callable接口都可以實現多線程,不過實現Runnable接口與實現Callable接口的方式基本相同,只是Callable接口裏定義的方法有返回值,可以聲明拋出異常而已。因此把Runnable接口和Callable接口歸爲一種方式。
採用實現Runnable、Callable接口的方式創建多線的優缺點:
-線程類只是實現了Runnable接口或Callable接口,還可以繼承其他類。
-在這種方式下,多個線程可以共享同一個target對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
-劣勢是編程稍稍複雜,如果需要訪問當前線程,則必須使用Thread.currentThread()方法。
採用繼承Thread類的方式創建多線程的優缺點:
-劣勢,不能繼承其它父類,因爲已經繼承了Thread類。
-優勢,編寫簡單,訪問當前線程直接使用this即可。
總體來講,一般採用Runnable接口和Callable接口的方式來創建多線程。