執行緒的三種實現方式
阿新 • • 發佈:2020-09-09
執行緒的三種實現方式
- 繼承Thread類(Thread類也實現了Runnable介面)
- 實現Runnable介面
- 實現Callable介面
繼承Thread類
程式碼實現
// 1. 繼承Thread類 public class MyThread extends Thread { // 2. 重寫run方法 @Override public void run() { // 執行緒體 for (int i = 0; i < 1000; i++) { System.out.println(Thread.currentThread().getName() + " *** " + i); } } public static void main(String[] args) { // 主執行緒體 // 3. 例項化執行緒物件 MyThread myThread = new MyThread(); // 4. 啟動執行緒 myThread.start(); // 主執行緒中其他操作 for (int i = 0; i < 1000; i++) { System.out.println(Thread.currentThread().getName() + " --- " + i); } } }
部分執行結果
main --- 0 main --- 1 main --- 2 main --- 3 main --- 4 main --- 5 main --- 6 main --- 7 main --- 8 main --- 9 main --- 10 main --- 11 main --- 12 main --- 13 main --- 14 main --- 15 main --- 16 main --- 17 main --- 18 main --- 19 main --- 20 main --- 21 main --- 22 main --- 23 main --- 24 main --- 25 main --- 26 main --- 27 main --- 28 main --- 29 main --- 30 main --- 31 main --- 32 Thread-0 *** 0 Thread-0 *** 1 Thread-0 *** 2 Thread-0 *** 3 Thread-0 *** 4 Thread-0 *** 5 Thread-0 *** 6 Thread-0 *** 7 Thread-0 *** 8 Thread-0 *** 9 Thread-0 *** 10 Thread-0 *** 11 Thread-0 *** 12 Thread-0 *** 13 Thread-0 *** 14 main --- 33 main --- 34 main --- 35 main --- 36 main --- 37
可以看出
- 執行緒啟動不一定立即執行,需要等待CPU排程
- 多個執行緒交替執行,同一時刻只有一個執行緒執行
- 每次執行結果不同,執行時隨機的
使用多執行緒下載檔案
實現多執行緒下載網路圖片到本地,使用commons-io包進行下載。
新增maven依賴
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
編寫工具類
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* 檔案下載工具類
*/
public class DownloadUtil {
/**
* 下載網路圖片到本地
* @param url
* @param fileName
* @return
*/
public static void download(String url, String fileName) {
try {
// 下載檔案放到本地
FileUtils.copyURLToFile(new URL(url), new File(fileName));
} catch (IOException e) {
e.printStackTrace();
}
}
}
建立自己的執行緒類並編寫測試程式碼
import java.io.File;
// 編寫自己的執行緒類繼承Thread類
public class MyThread extends Thread {
private String url; // 網路圖片地址
private String fileName; // 檔名
// 通過有參構造傳url地址和檔名
public MyThread(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}
// 重寫父類的run方法
@Override
public void run() {
// 呼叫寫好的工具類,下載檔案到本地
DownloadUtil.download(url, fileName);
System.out.println(fileName + " 下載完畢!");
}
// main方法測試
public static void main(String[] args) {
// 建立三個執行緒
MyThread myThread1 = new MyThread("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1877714368,4013968043&fm=26&gp=0.jpg", "E:" + File.separator + "1.jpg");
MyThread myThread2 = new MyThread("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3824205454,170685433&fm=26&gp=0.jpg", "E:" + File.separator + "2.jpg");
MyThread myThread3 = new MyThread("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1599658994385&di=5546e75c68f9ccb7b6e887525f979b38&imgtype=0&src=http%3A%2F%2Fwww.bizhidaquan.com%2Fd%2Ffile%2Fdongwu%2Fyesheng%2F2015-01-19%2F35cda34d5345e6806aec2a081ae2181b.jpg", "E:" + File.separator + "3.jpg");
// 啟動執行緒
myThread1.start();
myThread2.start();
myThread3.start();
}
}
控制檯列印結果
E:\2.jpg 下載完畢!
E:\1.jpg 下載完畢!
E:\3.jpg 下載完畢!
在E盤可以找到對應的檔案
實現Runnable介面
程式碼實現
// 1. 實現Runnable介面
public class MyThread implements Runnable {
// 2. 實現run()方法
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + " *** " + i);
}
}
// 測試程式碼
public static void main(String[] args) {
// 3. 建立例項化物件
MyThread myThread = new MyThread();
// 4. 建立Thread類的例項化物件,將自定義執行緒類的例項化物件作為引數
Thread thread = new Thread(myThread);
// 5. 呼叫Thread類的start()方法啟動執行緒
thread.start();
// 主執行緒中的其他操作
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + " --- " + i);
}
}
}
執行結果和繼承Thread實現是一樣的
main --- 45
main --- 46
Thread-0 *** 0
Thread-0 *** 1
Thread-0 *** 2
Thread-0 *** 3
Thread-0 *** 4
Thread-0 *** 5
Thread-0 *** 6
Thread-0 *** 7
Thread-0 *** 8
Thread-0 *** 9
Thread-0 *** 10
Thread-0 *** 11
Thread-0 *** 12
Thread-0 *** 13
Thread-0 *** 14
Thread-0 *** 15
Thread-0 *** 16
Thread-0 *** 17
Thread-0 *** 18
Thread-0 *** 19
Thread-0 *** 20
Thread-0 *** 21
Thread-0 *** 22
Thread-0 *** 23
Thread-0 *** 24
Thread-0 *** 25
Thread-0 *** 26
Thread-0 *** 27
Thread-0 *** 28
Thread-0 *** 29
Thread-0 *** 30
Thread-0 *** 31
Thread-0 *** 32
Thread-0 *** 33
Thread-0 *** 34
Thread-0 *** 35
Thread-0 *** 36
Thread-0 *** 37
Thread-0 *** 38
Thread-0 *** 39
Thread-0 *** 40
Thread-0 *** 41
Thread-0 *** 42
Thread-0 *** 43
Thread-0 *** 44
Thread-0 *** 45
Thread-0 *** 46
Thread-0 *** 47
Thread-0 *** 48
Thread-0 *** 49
Thread-0 *** 50
Thread-0 *** 51
Thread-0 *** 52
Thread-0 *** 53
main --- 47
main --- 48
main --- 49
main --- 50
main --- 51
main --- 52
main --- 53
main --- 54
實現Runnable介面建立多執行緒實際上是靜態代理的實現
Runnable是共同的介面,Thread是代理類,自己實現的MyThread類是被代理類。
呼叫Thread的有參構造時,將真實物件(MyThread的例項化物件)傳給代理物件
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
在Thread類的init()方法中,把target值賦給了Thread的屬性target,也就是改引用指向了MyThread類的例項化物件。
this.target = target;
在Thread的run()方法中又呼叫了MyThread例項化物件的run()方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
多個執行緒同時操作同一個物件
// 模擬買票
public class MyThread implements Runnable {
private int num = 10; // 總票數
@Override
public void run() {
while (true) {
// 如果無票,退出迴圈
if (num <= 0) {
break;
}
try {
// 模擬延時
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " --> 買到了第" + this.num-- + "張票");
}
}
// 測試程式碼
public static void main(String[] args) {
MyThread myThread = new MyThread();
// 多個執行緒操作同一個物件
new Thread(myThread, "張三").start();
new Thread(myThread, "李四").start();
new Thread(myThread, "王五").start();
}
}
執行結果
張三 --> 買到了第10張票
李四 --> 買到了第9張票
王五 --> 買到了第8張票
李四 --> 買到了第7張票
張三 --> 買到了第7張票
王五 --> 買到了第6張票
李四 --> 買到了第5張票
張三 --> 買到了第4張票
王五 --> 買到了第3張票
李四 --> 買到了第2張票
張三 --> 買到了第1張票
王五 --> 買到了第0張票
發現第7張票同時被兩個人買到了,王五買到了第0張票
由此可見,多個執行緒同時操作同一資源時,執行緒是不安全的
龜兔賽跑
程式碼實現
// 賽道是公用的
public class Race implements Runnable {
private String winner; // 存放獲勝者
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
// 如果獲勝者不為空,說明比賽結束
if (this.winner != null) {
break;
}
// 模擬兔子睡覺
if ("兔子".equals(Thread.currentThread().getName()) && i % 50 == 0) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " --> 跑了" + i + "米");
// 如果超過100米,當前執行緒為獲勝者
if (i >= 100) {
this.winner = Thread.currentThread().getName();
System.out.println("獲勝者是 --> " + this.winner);
}
}
}
// 測試程式碼
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "兔子").start();
new Thread(race, "烏龜").start();
}
}
執行結果
烏龜 --> 跑了0米
烏龜 --> 跑了1米
烏龜 --> 跑了2米
烏龜 --> 跑了3米
烏龜 --> 跑了4米
烏龜 --> 跑了5米
烏龜 --> 跑了6米
烏龜 --> 跑了7米
烏龜 --> 跑了8米
烏龜 --> 跑了9米
烏龜 --> 跑了10米
烏龜 --> 跑了11米
烏龜 --> 跑了12米
烏龜 --> 跑了13米
烏龜 --> 跑了14米
烏龜 --> 跑了15米
烏龜 --> 跑了16米
烏龜 --> 跑了17米
烏龜 --> 跑了18米
烏龜 --> 跑了19米
烏龜 --> 跑了20米
烏龜 --> 跑了21米
烏龜 --> 跑了22米
烏龜 --> 跑了23米
烏龜 --> 跑了24米
烏龜 --> 跑了25米
烏龜 --> 跑了26米
烏龜 --> 跑了27米
烏龜 --> 跑了28米
烏龜 --> 跑了29米
烏龜 --> 跑了30米
烏龜 --> 跑了31米
烏龜 --> 跑了32米
烏龜 --> 跑了33米
烏龜 --> 跑了34米
烏龜 --> 跑了35米
烏龜 --> 跑了36米
烏龜 --> 跑了37米
烏龜 --> 跑了38米
烏龜 --> 跑了39米
烏龜 --> 跑了40米
烏龜 --> 跑了41米
烏龜 --> 跑了42米
烏龜 --> 跑了43米
烏龜 --> 跑了44米
烏龜 --> 跑了45米
烏龜 --> 跑了46米
烏龜 --> 跑了47米
烏龜 --> 跑了48米
烏龜 --> 跑了49米
烏龜 --> 跑了50米
烏龜 --> 跑了51米
烏龜 --> 跑了52米
烏龜 --> 跑了53米
烏龜 --> 跑了54米
烏龜 --> 跑了55米
烏龜 --> 跑了56米
烏龜 --> 跑了57米
烏龜 --> 跑了58米
烏龜 --> 跑了59米
烏龜 --> 跑了60米
烏龜 --> 跑了61米
烏龜 --> 跑了62米
烏龜 --> 跑了63米
烏龜 --> 跑了64米
烏龜 --> 跑了65米
烏龜 --> 跑了66米
烏龜 --> 跑了67米
烏龜 --> 跑了68米
兔子 --> 跑了0米
兔子 --> 跑了1米
兔子 --> 跑了2米
兔子 --> 跑了3米
兔子 --> 跑了4米
兔子 --> 跑了5米
兔子 --> 跑了6米
兔子 --> 跑了7米
兔子 --> 跑了8米
兔子 --> 跑了9米
兔子 --> 跑了10米
兔子 --> 跑了11米
兔子 --> 跑了12米
兔子 --> 跑了13米
兔子 --> 跑了14米
兔子 --> 跑了15米
兔子 --> 跑了16米
兔子 --> 跑了17米
兔子 --> 跑了18米
兔子 --> 跑了19米
兔子 --> 跑了20米
兔子 --> 跑了21米
兔子 --> 跑了22米
兔子 --> 跑了23米
兔子 --> 跑了24米
兔子 --> 跑了25米
兔子 --> 跑了26米
兔子 --> 跑了27米
兔子 --> 跑了28米
兔子 --> 跑了29米
兔子 --> 跑了30米
兔子 --> 跑了31米
兔子 --> 跑了32米
兔子 --> 跑了33米
兔子 --> 跑了34米
兔子 --> 跑了35米
兔子 --> 跑了36米
兔子 --> 跑了37米
兔子 --> 跑了38米
兔子 --> 跑了39米
兔子 --> 跑了40米
兔子 --> 跑了41米
兔子 --> 跑了42米
兔子 --> 跑了43米
兔子 --> 跑了44米
兔子 --> 跑了45米
兔子 --> 跑了46米
兔子 --> 跑了47米
兔子 --> 跑了48米
兔子 --> 跑了49米
烏龜 --> 跑了69米
烏龜 --> 跑了70米
烏龜 --> 跑了71米
烏龜 --> 跑了72米
烏龜 --> 跑了73米
烏龜 --> 跑了74米
烏龜 --> 跑了75米
烏龜 --> 跑了76米
烏龜 --> 跑了77米
烏龜 --> 跑了78米
烏龜 --> 跑了79米
烏龜 --> 跑了80米
烏龜 --> 跑了81米
烏龜 --> 跑了82米
烏龜 --> 跑了83米
烏龜 --> 跑了84米
烏龜 --> 跑了85米
烏龜 --> 跑了86米
烏龜 --> 跑了87米
烏龜 --> 跑了88米
烏龜 --> 跑了89米
烏龜 --> 跑了90米
烏龜 --> 跑了91米
烏龜 --> 跑了92米
烏龜 --> 跑了93米
烏龜 --> 跑了94米
烏龜 --> 跑了95米
烏龜 --> 跑了96米
烏龜 --> 跑了97米
烏龜 --> 跑了98米
烏龜 --> 跑了99米
烏龜 --> 跑了100米
獲勝者是 --> 烏龜
兔子 --> 跑了50米
實現Callable介面
程式碼實現
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 1. 實現Callable介面,返回值型別String
public class MyCallable implements Callable<String> {
// 2. 實現call()方法
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " --- " + i);
}
return Thread.currentThread().getName();
}
// 測試程式碼
public static void main(String[] args) {
// 3. 建立MyCallable例項化物件
MyCallable myCallable = new MyCallable();
// 4. 將MyCallable例項化物件包裝成FutureTask
// 繼承關係: FutureTask實現了RunnableFuture介面,RunnableFuture介面繼承了Runnable介面
FutureTask<String> futureTask = new FutureTask<>(myCallable);
// 5. 建立Thread例項化物件
Thread thread = new Thread(futureTask);
// 6. 呼叫start()方法啟動執行緒
thread.start();
// 7. 獲取返回值
String result = null;
try {
result = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("call()返回值:" + result);
}
}
FutureTask實現了RunnableFuture介面,RunnableFuture介面繼承了Runnable介面,傳遞給Thread類構造方法的依然是Runnable的實現類。
因為FutureTask實現了Runnable介面,那麼FutureTask必定實現了run()方法,並在run()方法中呼叫了Callable實現類中的call()方法,本質還是實現Runnable介面的run()方法作為執行緒體,呼叫Thread類的start()方法啟動執行緒
實現Callable和前兩種方式的不同之處在於:
- call()方法有返回值
- call()方法可以丟擲異常
總結
以上三種方式實際上都是通過Thread類建立並啟動執行緒,啟動執行緒的方式只有一種,就是呼叫Thread類的start()方法