0. 環境:
電腦:Windows10
Android Studio: 2024.3.2
編程語言: Java
Gradle version:8.11.1
Compile Sdk Version:35
Java 版本:Java11
1. 先熟悉JVM虛擬機的線程
----------以下都是系統線程,由JVM管理,通常無需直接操作,僅需了解即可。----------
先創建一個Java module(創建方法可以看我這篇文章的3.1)
3.1 創建module
首先,創建一個module:菜單--file--new--new module...
?
注意左側選擇Java or Kotlin Library,右側填寫相應信息。點finish
?
?然后執行以下代碼:
public static void main(String[] args) {// 虛擬機線程管理的接口ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();// 取得線程信息ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);for (ThreadInfo threadInfo : threadInfos) {String logInfo = String.format("[%s] + %s", threadInfo.getThreadId(), threadInfo.getThreadName());// 打印線程Id和線程名稱System.out.println(logInfo);}
}
運行后看結果:
[1] + main
[9] + Reference Handler
[10] + Finalizer
[11] + Signal Dispatcher
[12] + Attach Listener
[19] + Notification Thread
[20] + Common-Cleaner
---線程名稱--- | ---作用--- |
main | 主線程,程序入口 |
Reference Handler | 處理引用對象 |
Finalizer | 執行對象的finalize() 方法 |
Signal Dispatcher | 處理操作系統信號 |
Attach Listener | 支持動態附加(Attach)到JVM |
Notification Thread | 處理JMX(Java管理擴展)通知 |
Common-Cleaner | 替代Finalizer 的輕量級清理線程(Java 9+引入) |
(沒有看到GC線程,是因為目前沒有資源需要回收。當出現資源需要回收時,才會觸發JVM的GC線程。此時才有GC線程來回收資源。也就是GC回收機制)
----------以上都是系統線程,由JVM管理,通常無需直接操作,僅需了解即可。----------
?2. 運行線程的方式
三種方式:
--名稱-- | 備注 |
Thread | 線程 |
Runable | 任務,需要通過thread來運行該任務 |
Callable | 有返回值的任務,需要通過futureTask來掛載 |
示例代碼:
// 第一種方式:thread
private static class Thread1 extends Thread {@Overridepublic void run() {super.run();System.out.println("--- run thread1 ---");}
}
// 第二種方式:runnable
private static class Runnable2 implements Runnable {@Overridepublic void run() {System.out.println("--- run runnable2 ---");}
}
// 第三種方式:callable
private static class Callable3 implements Callable<String> {@Overridepublic String call() throws Exception {System.out.println("--- run callable3 ---");return "[result]: run success";}
}
運行方式如下:
public static void main(String[] args) {// 方法一的線程可以通過start()方法直接運行Thread1 thread1 = new Thread1();thread1.start(); // 需要通過start()來啟動線程。run()函數不是啟動線程,只是函數調用// 方法二的任務,需要通過線程來執行Runnable2 runnable2 = new Runnable2();Thread thread = new Thread(runnable2);thread.start();// 方法三的有返回值的任務,需要掛載在FutureTask,再通過線程來執行Callable3 callable3 = new Callable3();// 將callable掛載在futureTask上FutureTask<String> futureTask = new FutureTask<>(callable3);// 通過線程來執行futureTask任務Thread callableThread = new Thread(futureTask);callableThread.start();// 此時我們可以獲取callable的返回值try {System.out.println("callable return: " + futureTask.get());} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}
}
運行后,可以看到結果:
--- run thread1 ---
--- run runnable2 ---
--- run callable3 ---
callable return: [result]: run success
3. 停止線程的方式
1. stop()停止(不推薦!)
非常不推薦使用stop()來停止線程,過時了。該函數屬于暴力停止,有一定的危險性。例如來不及釋放、會產生碎片等問題。甚至會被其他程序員鄙視(開玩笑的)
你就想吧,你在KTV里唱歌,唱到一半被人切歌了。你是不是想給他一拳
2. interrupt()中斷
優雅的停止線程的方式,應該是讓run()函數執行完之后,不再執行。
interrupt()是中斷信號的函數
private static class InterruptThread extends Thread {@Overridepublic void run() {super.run();String threadInfo = String.format("[%s] %s", Thread.currentThread().getId(), Thread.currentThread().getName());String isRunning = String.format("%s is Running", threadInfo);String isStopped = String.format("%s is Stopped", threadInfo);while (!isInterrupted()) { // 注意這里,需要增加 中斷信號的判斷System.out.println(isRunning);}System.out.println(isStopped);// -------------以下為錯誤示范---------------/**while (true) { // 如果判斷條件為true時,即使調用了thread.interrupt(). 該線程也無法停止System.out.println(isRunning);}System.out.println(isStopped);*/}
}
執行代碼方法如下:
InterruptThread thread = new InterruptThread();
thread.start(); // 開啟線程
try {Thread.sleep(10);
} catch (InterruptedException e) {throw new RuntimeException(e);
}
thread.interrupt(); // 發起中斷信號
運行后,可以看到結果:
[21] Thread-0 is Running
[21] Thread-0 is Running
·····
[21] Thread-0 is Running
[21] Thread-0 is Running
[21] Thread-0 is Stopped
?最后確實停止了。
中斷后,沒有重啟的概念。只有線程啟動、線程中斷(也就是停止)。要想再次啟動線程,需要再次使用線程啟動。
注意:
如果在run()函數中使用了sleep(),會導致interrupt信號被清除。如果需要使用sleep()函數,需要在拋異常部分,再次發送一次interrupt(); 這樣即可中斷線程。
參考示例:
private static class Thread1 extends Thread {@Overridepublic void run() {super.run();try {System.out.println("--- run thread1 ---");Thread.sleep(1000);} catch (InterruptedException e) {interrupt(); // 再次發送中斷信號e.printStackTrace();}}
}
擴展:
如果不是繼承Thread,而是實現Runnable,需要怎么改呢?
請看代碼:
private static class InterruptThread2 implements Runnable {@Overridepublic void run() {String threadInfo = String.format("[%s] %s", Thread.currentThread().getId(), Thread.currentThread().getName());String isRunning = String.format("%s is Running", threadInfo);String isStopped = String.format("%s is Stopped", threadInfo);// 通過Thread.currentThread(),來獲取線程while (!Thread.currentThread().isInterrupted()) {System.out.println(isRunning);}System.out.println(isStopped);}
}
4. 補充一個join()函數
join()的意思是放棄當前線程的執行,并返回對應的線程。
如果不是用join()會導致線程之間隨機執行,無法做到控制順序。
應用場景:執行完線程1后,需要執行線程2,則可以對線程1執行join()函數,再對線程2執行start()函數。達到先執行線程1,再執行線程2,從而達到控制線程執行順序的效果。
貼代碼
private static class InterruptThread extends Thread {public InterruptThread(String s) {super(s);//傳入線程名稱,用于區分不同線程}@Overridepublic void run() {super.run();String threadInfo = String.format("[%s] %s", Thread.currentThread().getId(), Thread.currentThread().getName());for (int i = 0; i < 100; i++) {System.out.println(i + ";" + getName() + threadInfo);}}
}
//執行代碼:
InterruptThread thread1 = new InterruptThread("A");
InterruptThread thread2 = new InterruptThread("B");
thread1.start();
// thread1.join();//注意這個join。可以做到先執行thread1
thread2.start();
執行后看結果:
0;B[22] B
1;B[22] B
2;B[22] B
3;B[22] B
4;B[22] B
5;B[22] B
6;B[22] B
0;A[21] A
7;B[22] B
1;A[21] A
8;B[22] B
9;B[22] B
10;B[22] B
11;B[22] B
12;B[22] B
13;B[22] B
14;B[22] B
15;B[22] B
2;A[21] A
16;B[22] B
3;A[21] A
17;B[22] B
4;A[21] A
18;B[22] B
···
可以看到線程A和線程B混在一起,無法做到先后執行。如果使用了thread1.join(); 再運行之后,就可以做到執行完線程A后,再開始執行線程B。?
運行結果:
0;A[21] A
1;A[21] A
2;A[21] A
3;A[21] A
4;A[21] A
···
97;A[21] A
98;A[21] A
99;A[21] A
// 可以看到,完全執行完線程A之后,才會執行線程B
0;B[22] B
1;B[22] B
2;B[22] B
3;B[22] B
4;B[22] B
···
97;B[22] B
98;B[22] B
99;B[22] B
?5. sleep()和wait()的區別
sleep | wait |
是休眠 | 是等待 |
可以無條件休眠 | 特殊原因需要等待 (例如:資源不足) |
休眠需要帶參: 休眠時間 | 調用不用帶參 |
休眠結束后,立刻有執行權 | 需要手動喚醒,才有執行權 |
sleep | wait |
是休眠 | 是等待 |
可以無條件休眠 | 特殊原因需要等待 (例如:資源不足) |
休眠需要帶參: 休眠時間 | 調用不用帶參 |
休眠結束后,立刻有執行權 | 需要手動喚醒,才有執行權 |
會清除interrupt信號 |
?執行權并不代表馬上執行。什么時候執行 取決于操作系統的調度。
寫在最后
至此,我們就學會了線程的啟動和停止。順便學會了順序執行線程。