1. 創建線程的三種方法及其區別
1.1 繼承Thread類
首先,定義Thread類的子類並重寫run()方法:
package com.zwwhnly.springbootaction.javabase.thread;
public class MyFirstThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.printf("[MyFirstThread]輸出:%d,當前線程名稱:%s\n",
i, getName());
}
}
}
然後,創建該子類的實例並調用start()方法啟動線程:
package com.zwwhnly.springbootaction.javabase.thread;
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主線程開始執行,當前線程名稱:" +
Thread.currentThread().getName());
Thread firstThread = new MyFirstThread();
firstThread.start();
System.out.println("主線程執行結束,當前線程名稱:" +
Thread.currentThread().getName());
}
}
運行結果如下所示:
主線程開始執行,當前線程名稱:main
主線程執行結束,當前線程名稱:main
[MyFirstThread]輸出:0,當前線程名稱:Thread-0
[MyFirstThread]輸出:1,當前線程名稱:Thread-0
[MyFirstThread]輸出:2,當前線程名稱:Thread-0
[MyFirstThread]輸出:3,當前線程名稱:Thread-0
[MyFirstThread]輸出:4,當前線程名稱:Thread-0
從運行結果可以看出以下2個問題:
- 程序中存在2個線程,分別為主線程main和自定義的線程Thread-0。
- 調用
firstThread.start();
,run()方法體中的代碼並沒有立即執行,而是異步執行的。
查看Thread類的源碼,可以發現Thread類實現了接口Runnable:
public class Thread implements Runnable {
// 省略其它代碼
}
這裡是重點,面試常問!
1.2 實現Runnable接口(推薦)
首先,定義Runnable接口的實現類並實現run()方法:
package com.zwwhnly.springbootaction.javabase.thread;
public class MySecondThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.printf("[MySecondThread]輸出:%d,當前線程名稱:%s\n",
i, Thread.currentThread().getName());
}
}
}
然後,調用Thread類的構造函數創建Thread實例並調用start()方法啟動線程:
package com.zwwhnly.springbootaction.javabase.thread;
public class ThreadTest {
public static void main(String[] args) {
Runnable target = new MySecondThread();
Thread secondThread = new Thread(target);
secondThread.start();
}
}
運行結果如下所示:
主線程開始執行,當前線程名稱:main
主線程執行結束,當前線程名稱:main
[MySecondThread]輸出:0,當前線程名稱:Thread-0
[MySecondThread]輸出:1,當前線程名稱:Thread-0
[MySecondThread]輸出:2,當前線程名稱:Thread-0
[MySecondThread]輸出:3,當前線程名稱:Thread-0
[MySecondThread]輸出:4,當前線程名稱:Thread-0
可以看出,使用這種方式和繼承Thread類的運行結果是一樣的。
1.3 實現Callable接口
首先,定義Callable接口的實現類並實現call()方法:
package com.zwwhnly.springbootaction.javabase.thread;
import java.util.Random;
import java.util.concurrent.Callable;
public class MyThirdThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(6 * 1000);
return new Random().nextInt();
}
}
然後,調用FutureTask類的構造函數創建FutureTask實例:
Callable<Integer> callable = new MyThirdThread();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
最後,調用Thread類的構造函數創建Thread實例並調用start()方法啟動線程:
package com.zwwhnly.springbootaction.javabase.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主線程開始執行,當前線程名稱:" +
Thread.currentThread().getName());
Callable<Integer> callable = new MyThirdThread();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
try {
System.out.println("futureTask.isDone() return:" + futureTask.isDone());
System.out.println(futureTask.get());
System.out.println("futureTask.isDone() return:" + futureTask.isDone());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("主線程執行結束,當前線程名稱:" +
Thread.currentThread().getName());
}
}
運行結果如下所示:
主線程開始執行,當前線程名稱:main
futureTask.isDone() return:false
-1193053528
futureTask.isDone() return:true
主線程執行結束,當前線程名稱:main
可以發現,使用Callable接口這種方式,我們可以通過futureTask.get()
獲取到線程的執行結果,而之前的2種方式,都是沒有返回值的。
注意事項:調用futureTask.get()
獲取線程的執行結果時,主線程會阻塞直到獲取到結果。
阻塞效果如下圖所示:
1.4 區別
以下是重點,面試常問!
- Java中,類僅支持單繼承,如果一個類繼承了Thread類,就無法再繼承其它類,因此,如果一個類既要繼承其它的類,又必須創建為一個線程,就可以使用實現Runable接口的方式。
- 使用實現Runable接口的方式創建的線程可以處理同一資源,實現資源的共享。
- 使用實現Callable接口的方式創建的線程,可以獲取到線程執行的返回值、是否執行完成等信息。
關於第2點,可以通過如下示例來理解。
假如我們總共有10張票(共享的資源),為了提升售票的效率,開了3個線程來售賣,代碼如下所示:
package com.zwwhnly.springbootaction.javabase.thread;
public class SaleTicketThread implements Runnable {
private int quantity = 10;
@Override
public void run() {
while (quantity > 0) {
System.out.println(quantity-- + " is saled by " +
Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
Runnable runnable = new SaleTicketThread();
Thread saleTicketThread1 = new Thread(runnable);
Thread saleTicketThread2 = new Thread(runnable);
Thread saleTicketThread3 = new Thread(runnable);
saleTicketThread1.start();
saleTicketThread2.start();
saleTicketThread3.start();
}
因為3個線程都是異步執行的,因此每次的運行結果可能是不一樣,以下列舉2次不同的運行結果。
第1次運行結果:
10 is saled by Thread-0
8 is saled by Thread-0
7 is saled by Thread-0
5 is saled by Thread-0
9 is saled by Thread-1
3 is saled by Thread-1
2 is saled by Thread-1
1 is saled by Thread-1
4 is saled by Thread-0
6 is saled by Thread-2
第2次運行結果:
10 is saled by Thread-0
9 is saled by Thread-0
8 is saled by Thread-0
7 is saled by Thread-0
6 is saled by Thread-0
5 is saled by Thread-0
3 is saled by Thread-0
2 is saled by Thread-0
4 is saled by Thread-2
1 is saled by Thread-1
如果將上面的SaleTicketThread修改成繼承Thread類的方式,就變成了3個線程各自擁有10張票,即變成了30張票,而不是3個線程共享10張票。
2. Thread類start()和run()的區別
2.1 示例
因為實現Runnable接口的優勢,基本上實現多線程都使用的是該種方式,所以我們將之前定義的MyFirstThread也修改為實現Runnable接口的方式:
package com.zwwhnly.springbootaction.javabase.thread;
public class MyFirstThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.printf("[MyFirstThread]輸出:%d,當前線程名稱:%s\n",
i, Thread.currentThread().getName());
}
}
}
然後仍然沿用之前定義的MyFirstThread、MySecondThread,我們先看下調用start()的效果:
package com.zwwhnly.springbootaction.javabase.thread;
public class ThreadTest {
public static void main(String[] args) {
System.out.println("主線程開始執行,當前線程名稱:" +
Thread.currentThread().getName());
Thread firstThread = new Thread(new MyFirstThread());
Runnable target = new MySecondThread();
Thread secondThread = new Thread(target);
firstThread.start();
secondThread.start();
System.out.println("主線程執行結束,當前線程名稱:" +
Thread.currentThread().getName());
}
}
運行結果(注意:多次運行,結果可能不一樣):
主線程開始執行,當前線程名稱:main
[MyFirstThread]輸出:0,當前線程名稱:Thread-0
[MyFirstThread]輸出:1,當前線程名稱:Thread-0
[MySecondThread]輸出:0,當前線程名稱:Thread-1
主線程執行結束,當前線程名稱:main
[MySecondThread]輸出:1,當前線程名稱:Thread-1
[MySecondThread]輸出:2,當前線程名稱:Thread-1
[MySecondThread]輸出:3,當前線程名稱:Thread-1
[MySecondThread]輸出:4,當前線程名稱:Thread-1
[MyFirstThread]輸出:2,當前線程名稱:Thread-0
[MyFirstThread]輸出:3,當前線程名稱:Thread-0
[MyFirstThread]輸出:4,當前線程名稱:Thread-0
可以看出,調用start()方法后,程序中有3個線程,分別為主線程main、Thread-0、Thread-1,而且執行順序不是按順序執行的,存在不確定性。
然後將start()方法修改為run()方法,如下所示:
firstThread.run();
secondThread.run();
此時的運行結果如下所示(多次運行,結果是一樣的):
主線程開始執行,當前線程名稱:main
[MyFirstThread]輸出:0,當前線程名稱:main
[MyFirstThread]輸出:1,當前線程名稱:main
[MyFirstThread]輸出:2,當前線程名稱:main
[MyFirstThread]輸出:3,當前線程名稱:main
[MyFirstThread]輸出:4,當前線程名稱:main
[MySecondThread]輸出:0,當前線程名稱:main
[MySecondThread]輸出:1,當前線程名稱:main
[MySecondThread]輸出:2,當前線程名稱:main
[MySecondThread]輸出:3,當前線程名稱:main
[MySecondThread]輸出:4,當前線程名稱:main
主線程執行結束,當前線程名稱:main
可以看出,調用run()方法后,程序中只有一個主線程,自定義的2個線程並沒有啟動,而且執行順序也是按順序執行的。
1.2 總結
以下是重點,面試常問!
- run()方法只是一個普通方法,調用之後程序會等待run()方法執行完畢,所以是串行執行,而不是并行執行。
- start()方法會啟動一個線程,當線程得到CPU資源後會自動執行run()方法體中的內容,實現真正的併發執行。
3. Runnable和Callable的區別
在文章前面的章節中(1.2 實現Runnable接口 和1.3 實現Callable接口),我們了解了如何使用Runnable、Callable接口來創建線程,現在我們分別看下Runable和Callable接口的定義,其中,Runable接口的定義如下所示:
public interface Runnable {
public abstract void run();
}
Callable接口的定義如下所示:
public interface Callable<V> {
V call() throws Exception;
}
由此可以看出,Runnable和Callable的區別主要有以下幾點:
- Runable的執行方法是run(),Callable的執行方法是call()
- call()方法可以拋出異常,run()方法如果有異常只能在內部消化
- 實現Runnable接口的線程沒有返回值,實現Callable接口的線程能返回執行結果
- 實現Callable接口的線程,可以和FutureTask一起使用,獲取到線程是否完成、線程是否取消、線程執行結果,也可以取消線程的執行。
4. 源碼及參考
源碼地址:,歡迎下載。
如果覺得文章寫的不錯,歡迎關注我的微信公眾號:「申城異鄉人」,所有博客會同步更新。
如果有興趣,也可以添加我的微信:zwwhnly_002,一起交流和探討技術。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※廣受好評的【機場接送推薦】
※租車接送服務需要另外加價嗎?
※台東人氣名產、台東人氣伴手禮,熱門獨家商品,現貨供應中
※坐月子經驗談-新竹到府坐月子媽媽心得分享與交流
※給婚攝的迎娶婚禮錄影必拍清單,沒拍到您一定會後悔!
※旅遊不求人,帶你掌握小琉球民宿海景攻略
※各類招牌、海報、大圖輸出,急件製作施工!