本文開始將開始介紹 Java 多線程與並發相關的知識,多謝各位一直以來的關注與支持。關注我的公眾號「Java面典」了解更多 Java 相關知識點。
線程的創建方式
在 Java 中,用戶常用的主動創建線程的方式有三種,分別是 繼承 Thread 類、實現 Runnable 接口 、通過Callable
繼承 Thread 類
- 定義 Thread 類的子類,並重寫該類的 run 方法;
- 調用線程對象的 start() 方法來啟動該線程。
通過繼承 Thread 實現的線程類,多個線程間無法共享線程類的實例變量(需要創建不同 Thread 對象)。
/**
* 通過繼承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()方法來啟動該線程。
/**
* 通過實現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() 方法的返回值。
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<String> task = new FutureTask<Integer>((Callable<String>)()->{
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 循環中執行的。
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() 方法來中斷線程有兩種情況:
- 線程處於阻塞狀態。如使用了 sleep,同步鎖的 wait,socket 中的 receiver,accept 等方法時,會使線程處於阻塞狀態。
使用 interrupt 方法結束線程的時候,一定要先捕獲 InterruptedException 異常之后通過 break 來跳出循環,才能正常結束 run 方法。
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("線程已經退出!");
}
}
- 線程未處於阻塞狀態。使用 isInterrupted() 判斷線程的中斷標志來退出循環。當使用 interrupt() 方法時,中斷標志就會置 true,和使用自定義的標志來控制循環是一樣的道理。
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()) { //非阻塞過程中通過判斷中斷標志來退出
try {
Thread.sleep(5*1000);//阻塞過程捕獲中斷異常來退出
} catch (InterruptedException e) {
e.printStackTrace();
break;//捕獲到異常之后,執行 break 跳出循環
}
}
}
}
stop 方法終止線程
使用 stop 方法可以強行終止正在運行或掛起的線程。我們可以使用如下的代碼來終止線程:
thread.stop();
采用 stop 是不安全的,主要影響點如下:
- thread.stop() 調用之后,創建子線程的線程就會拋出 ThreadDeatherror 的錯誤;
- 調用 stop 會釋放子線程所持有的所有鎖。導致了該線程所持有的所有鎖的突然釋放(不可控制),那么被保護數據就有可能呈現不一致性。
總結
- 線程創建:推薦使用 Runnable 或者 Callable 方式創建線程,相比繼承,接口實現可以更加靈活,不會受限於Java的單繼承機制。
- 線程終止:線程終止推薦使用 標志位 或 Interrupt 方式終止,stop 方式對線程不安全,易導致數據不一致。