介紹
多線程從一開始就內置在 Java 中。那麼,讓我們簡單地看一下這個叫做多線程的東西。
![更好的結合:Java 和 Thread 類。 第一部分 — 執行線程 - 1]()
我們以 Oracle 的官方課程作為參考點:“
課程:“Hello World!”應用程序”。我們將稍微更改 Hello World 程序的代碼,如下所示:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
是程序啟動時傳遞的輸入參數數組。將此代碼保存到名稱與類名稱匹配且擴展名為
.java
.
使用javac實用程序編譯它:
javac HelloWorldApp.java
. 然後,我們用一些參數運行我們的代碼,例如,“Roger”:
java HelloWorldApp Roger
![更好的結合:Java 和 Thread 類。 第一部分 — 執行線程 - 2]()
我們的代碼目前有一個嚴重的缺陷。如果你不傳遞任何參數(即只執行“java HelloWorldApp”),那麼我們會得到一個錯誤:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
名為“main”的線程中發生異常(即錯誤)。那麼,Java有線程嗎?這是我們旅程的起點。
Java 和線程
要了解什麼是線程,您需要了解 Java 程序是如何啟動的。讓我們改變我們的代碼如下:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
// Do nothing
}
}
}
現在讓我們用
javac
. 為方便起見,我們將在單獨的窗口中運行我們的 Java 代碼。在 Windows 上,這可以像這樣完成
start java HelloWorldApp
:現在我們將使用
jps實用程序查看 Java 可以告訴我們哪些信息:
![更好的結合:Java 和 Thread 類。 第一部分 — 執行線程 - 3]()
第一個數字是 PID 或進程 ID。什麼是流程?
A process is a combination of code and data sharing a common virtual address space.
對於進程,不同的程序在運行時彼此隔離:每個應用程序在內存中使用自己的區域而不會干擾其他程序。要了解更多信息,我建議閱讀本教程:
進程和線程。一個進程不能沒有線程存在,所以如果一個進程存在,那麼它至少有一個線程。但是這在 Java 中是如何實現的呢?當我們啟動一個 Java 程序時,執行從
main
方法開始。就好像我們是在步入程序,所以這個特殊的
main
方法就叫做入口點。該
main
方法必須始終是“public static void”,這樣 Java 虛擬機 (JVM) 才能開始執行我們的程序。有關詳細信息,
為什麼 Java main 方法是靜態的?. 事實證明,Java 啟動程序(java.exe 或 javaw.exe)是一個簡單的 C 應用程序:它加載實際構成 JVM 的各種 DLL。Java 啟動器進行一組特定的 Java 本機接口 (JNI) 調用。JNI 是一種連接 Java 虛擬機世界和 C++ 世界的機制。因此,啟動器不是 JVM 本身,而是一種加載它的機制。它知道要執行的正確命令以啟動 JVM。它知道如何使用 JNI 調用來設置必要的環境。設置這個環境包括創建主線程,當然它被稱為“main”。為了更好地說明 Java 進程中存在哪些線程,我們使用
jvisualvm工具,它包含在 JDK 中。知道了一個進程的 pid,我們就可以立即看到關於該進程的信息:
jvisualvm --openpid <process id>
![更好的結合:Java 和 Thread 類。 第一部分 — 執行線程 - 4]()
有趣的是,每個線程在分配給該進程的內存中都有自己獨立的區域。這種內存結構稱為棧。堆棧由幀組成。幀表示方法的激活(未完成的方法調用)。框架也可以表示為 StackTraceElement(請參閱
StackTraceElement的 Java API )。您可以在此處的討論中找到有關分配給每個線程的內存的更多信息:“
Java (JVM) 如何為每個線程分配堆棧”。如果您查看
Java API並蒐索“Thread”一詞,您會找到
java.lang.Thread班級。這是在 Java 中代表線程的類,我們需要使用它。
java.lang.線程
在 Java 中,線程由類的實例表示
java.lang.Thread
。您應該立即明白 Thread 類的實例本身並不是執行線程。這只是一種由 JVM 和操作系統管理的低級線程的 API。當我們使用 Java 啟動器啟動 JVM 時,它會創建一個
main
名為“main”的線程和一些其他內務處理線程。正如 Thread 類的 JavaDoc 中所述:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
. 有兩種類型的線程:守護進程和非守護進程。守護線程是在後台執行某些工作的後台(內務處理)線程。“惡魔”一詞指的是麥克斯韋妖。您可以在這篇
維基百科文章中了解更多信息。如文檔中所述,JVM 會繼續執行程序(進程),直到:
- 調用Runtime.exit ()方法
- 所有非守護線程都完成了它們的工作(沒有錯誤或拋出異常)
一個重要的細節由此而來:守護線程可以在任何時候終止。因此,無法保證其數據的完整性。因此,守護線程適用於某些內務處理任務。例如,Java 有一個線程負責處理
finalize()
方法調用,即與垃圾收集器(gc) 相關的線程。每個線程都是組 (
ThreadGroup ) 的一部分。並且組可以是其他組的一部分,形成一定的層次結構或結構。
public static void main(String[] args) {
Thread currentThread = Thread.currentThread();
ThreadGroup threadGroup = currentThread.getThreadGroup();
System.out.println("Thread: " + currentThread.getName());
System.out.println("Thread Group: " + threadGroup.getName());
System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
組為線程管理帶來秩序。除了組之外,線程還有自己的異常處理程序。看一個例子:
public static void main(String[] args) {
Thread th = Thread.currentThread();
th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("An error occurred: " + e.getMessage());
}
});
System.out.println(2/0);
}
除以零將導致錯誤,該錯誤將被處理程序捕獲。如果您不指定自己的處理程序,那麼 JVM 將調用默認處理程序,它將異常的堆棧跟踪輸出到 StdError。每個線程也有一個優先級。您可以在本文中閱讀有關優先級的更多信息:
多線程中的 Java 線程優先級。
創建線程
如文檔中所述,我們有兩種創建線程的方法。第一種方法是創建自己的子類。例如:
public class HelloWorld{
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello, World!");
}
}
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
如您所見,任務的工作發生在
run()
方法中,但線程本身是在
start()
方法中啟動的。不要混淆這些方法:如果我們
un()
直接調用 r 方法,則不會啟動新線程。它是
start()
請求 JVM 創建新線程的方法。我們繼承 Thread 的這個選項已經很糟糕了,因為我們將 Thread 包含在我們的類層次結構中。第二個缺點是我們開始違反“單一責任”原則。也就是說,我們的類同時負責控制線程和在該線程中執行的某些任務。什麼是正確的方法?答案在
run()
我們覆蓋的相同方法中找到:
public void run() {
if (target != null) {
target.run();
}
}
這
target
是一些
java.lang.Runnable
,我們可以在創建 Thread 類的實例時傳遞它。這意味著我們可以這樣做:
public class HelloWorld{
public static void main(String[] args) {
Runnable task = new Runnable() {
public void run() {
System.out.println("Hello, World!");
}
};
Thread thread = new Thread(task);
thread.start();
}
}
Runnable
從 Java 1.8 開始也是函數式接口。這使得為線程的任務編寫更漂亮的代碼成為可能:
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
結論
我希望這個討論能夠闡明線程是什麼、線程是如何產生的以及線程可以執行哪些基本操作。在下
一部分中,我們將嘗試了解線程如何相互交互並探索線程生命週期。
更好的結合:Java 和 Thread 類。第二部分 — 同步 更好地結合:Java 和 Thread 類。第 III 部分 — 更好地交互:Java 和 Thread 類。第 IV 部分 — Callable、Future 和朋友 更好地結合在一起:Java 和 Thread 類。第五部分 — Executor、ThreadPool、Fork/Join 更好地結合在一起:Java 和 Thread 類。第六部分——開火!
GO TO FULL VERSION