Java多執行緒併發01——執行緒的建立與終止,你會幾種方式
阿新 • • 發佈:2020-03-16
> 本文開始將開始介紹 Java 多執行緒與併發相關的知識,多謝各位一直以來的關注與支援。關注我的公眾號「Java面典」瞭解更多 Java 相關知識點。
# 執行緒的建立方式
在 Java 中,使用者常用的主動建立執行緒的方式有三種,分別是 **繼承 Thread 類**、**實現 Runnable 介面** 、**通過Callable 和 Future**。
## 繼承 Thread 類
* 定義 Thread 類的子類,並重寫該類的 run 方法;
* 呼叫執行緒物件的 start() 方法來啟動該執行緒。
**通過繼承 Thread 實現的執行緒類,多個執行緒間無法共享執行緒類的例項變數(需要建立不同 Thread 物件)。**
```java
/**
* 通過繼承Thread實現執行緒
*/
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread = new MyThread();
myThread.start();
```
## 實現 Runnable 介面
* 如果自己的類已經 extends 另一個類,就無法直接 extends Thread,此時,可以實現一個 Runnable 介面;
* 呼叫執行緒物件的start()方法來啟動該執行緒。
```java
/**
* 通過實現Runnable介面實現的執行緒類
*/
public class RunnableTest implements Runnable {
@Override
public void run() {
System.out.println("RunnableTest.run()");
}
public static void main(String[] args) {
RunnableTest runnableTest = new RunnableTest() ;
Thread thread = new Thread(runnableTest);
thread.start();
}
}
```
## 通過 Callable、Future
從 Thread 和 Runnable 兩種方式可以看出,兩種方式都**不支援返回值**,且**不能宣告丟擲異常**。
而 Callable 介面則實現了此兩點,Callable 介面如同 Runable 介面的升級版,其提供的 call() 方法將作為執行緒的執行體,同時允許有返回值。
但是 Callable 物件不能直接作為 Thread 物件的 target,我們可以使用 FutureTask 類來包裝 Callable 物件,該 FutureTask 物件封裝了該 Callable 物件的 call() 方法的返回值。
```java
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) {
CallableTest callableTest = new CallableTest() ;
//因為Callable介面是函式式介面,可以使用Lambda表示式
FutureTask task = new FutureTask((Callable)()->{
System.out.println("FutureTask and Callable");
return "hello word";
});
try{
System.out.println("子執行緒返回值 : " + task.get());
} catch (Exception e){
e.printStackTrace();
}
}
}
```
# 執行緒的終止方式
執行緒除了正常結束外,還可以通過特定方式終止執行緒,終止執行緒常用的方式有以下三種:**使用退出標誌退出執行緒**、** Interrupt 方法結束執行緒**、**stop 方法終止執行緒**。
## 使用退出標誌退出執行緒
最常使用的方式其實現方式是:定義一個 boolean 型的標誌位,線上程的 run() 方法中根據這個標誌位是 true 還是 false 來判斷是否退出,這種情況一般是將任務放在 run() 方法中的一個 while 迴圈中執行的。
```java
public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() {
while (!exit){
//do work
}
}
public static void main(String[] args) throws Exception {
ThreadFlag thread = new ThreadFlag();
thread.start();
sleep(5000); // 主執行緒延遲5秒
thread.exit = true; // 終止執行緒thread
thread.join();
System.out.println("執行緒退出!");
}
}
```
## Interrupt 方法結束執行緒
使用 interrupt() 方法來中斷執行緒有兩種情況:
1. **執行緒處於阻塞狀態**。如使用了 sleep,同步鎖的 wait,socket 中的 receiver,accept 等方法時,會使執行緒處於阻塞狀態。`使用 interrupt 方法結束執行緒的時候,一定要先捕獲 InterruptedException 異常之後通過 break 來跳出迴圈,才能正常結束 run 方法。`
```java
public class ThreadInterrupt extends Thread {
public void run() {
try {
sleep(50000); // 延遲50秒
}
catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
public static void main(String[] args) throws Exception {
Thread thread = new ThreadInterrupt();
thread.start();
System.out.println("在50秒之內按任意鍵中斷執行緒!");
System.in.read();
thread.interrupt();
thread.join();
System.out.println("執行緒已經退出!");
}
}
```
2. **執行緒未處於阻塞狀態**。使用 isInterrupted() 判斷執行緒的中斷標誌來退出迴圈。當使用 interrupt() 方法時,中斷標誌就會置 true,和使用自定義的標誌來控制迴圈是一樣的道理。
```java
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()) { //非阻塞過程中通過判斷中斷標誌來退出
try {
Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出
} catch (InterruptedException e) {
e.printStackTrace();
break;//捕獲到異常之後,執行 break 跳出迴圈
}
}
}
}
```
## stop 方法終止執行緒
使用 stop 方法可以強行終止正在執行或掛起的執行緒。我們可以使用如下的程式碼來終止執行緒:
```java
thread.stop();
```
**採用 stop 是不安全的**,主要影響點如下:
1. thread.stop() 呼叫之後,建立子執行緒的執行緒就會丟擲 ThreadDeatherror 的錯誤;
2. 呼叫 stop 會釋放子執行緒所持有的所有鎖。導致了該執行緒所持有的所有鎖的突然釋放(不可控制),那麼被保護資料就有可能呈現不一致性。
# 總結
* **執行緒建立**:推薦使用 Runnable 或者 Callable 方式建立執行緒,相比繼承,介面實現可以更加靈活,不會受限於Java的單繼承機制。
* **執行緒終止**:執行緒終止推薦使用 標誌位 或 Interrupt 方式終止,stop 方式對執行緒不安全,易導致資料不