介紹
多線程從最早的時候就內建在 Java 中。那麼讓我們快速了解一下多線程是什麼。
讓我們以 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 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) {
}
}
}
現在讓我們使用 javac 再次編譯它。接下來,為了方便起見,我們將在單獨的視窗中執行 Java 程式碼。在 Windows 上,您可以像這樣執行此操作:
start java HelloWorldApp
。現在,使用
jps實用程序,讓我們看看 Java 將告訴我們哪些資訊:
第一個數字是 PID 或進程 ID,即進程標識符。什麼是流程?
Процесс — это совокупность codeа и данных, разделяющих общее виртуальное addressное пространство.
借助進程,不同程式的執行相互隔離:每個應用程式使用自己的記憶體區域,而不會幹擾其他程式。我建議您更詳細地閱讀這篇文章:「
https://habr.com/post/164487/」。進程不能沒有執行緒而存在,因此如果進程存在,則其中至少存在一個執行緒。在 Java 中這是如何發生的?當我們運行 Java 程式時,它的執行以
main
. 我們某種程度上進入了程序,所以這種特殊的方法
main
被稱為入口點,或「入口點」。這個方法
main
必須始終
public static void
如此,以便 Java 虛擬機器 (JVM) 可以開始執行我們的程式。有關更多詳細信息,請參閱“
為什麼 Java main 方法是靜態的? ”。事實證明,java啟動器(java.exe或javaw.exe)是一個簡單的應用程式(簡單的C應用程式):它載入各種DLL,這些DLL其實是JVM。Java 啟動器進行一組特定的 Java 本機介面 (JNI) 呼叫。JNI 是連結 Java 虛擬機器世界和 C++ 世界的機制。原來launcher並不是JVM,而是它的loader。它知道啟動 JVM 時要執行的正確命令。知道如何使用 JNI 呼叫組織所有必要的環境。這種環境的組織也包括主執行緒的創建,通常稱為
main
. 為了更清楚地了解 Java 進程中存在哪些線程,我們使用JDK 中包含的
jvisualvm程式。知道進程的 pid,我們可以立即打開它的資料:
jvisualvm --openpid айдипроцесса
有趣的是,每個執行緒在記憶體中都有自己獨立的區域分配給該進程。這種記憶體結構稱為堆疊。堆疊由幀組成。框架是呼叫方法的點、執行點。框架也可以表示為 StackTraceElement(請參閱
StackTraceElement的 Java API )。
您可以在此處閱讀有關分配給每個線程的記憶體的更多資訊。如果我們查看
Java API並蒐索 Thread 這個詞,我們會看到有一個類別
java.lang.Thread。正是這個類別在 Java 中代表了一個流,我們必須使用這個類別來工作。
java.lang.Thread
Java 中的執行緒表示為類別的實例
java.lang.Thread
。值得立即理解的是,Java 中 Thread 類別的實例本身並不是執行緒。這只是一種由 JVM 和作業系統管理的低階執行緒的 API。當我們使用 java 啟動器啟動 JVM 時,它會建立一個具有名稱的主執行緒
main
和多個服務執行緒。如同 Thread 類別的 JavaDoc 所述:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
執行緒有 2 種類型:守護程式和非守護程式。守護線程是在後台執行某些工作的後台線程(服務線程)。這個有趣的術語是對「麥克斯韋妖」的引用,您可以在維基百科有關「
惡魔」的文章中閱讀更多相關資訊。如文件中所述,JVM 繼續執行程序(進程),直到:
因此,重要的細節是:守護線程可以在任何正在執行的命令上終止。因此,無法保證其中資料的完整性。因此,守護線程適合一些服務任務。例如,在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);
}
除以零將導致錯誤,該錯誤將被處理程序捕獲。如果您自己不指定處理程序,則預設處理程序實作將起作用,它將在 StdError 中顯示錯誤堆疊。您可以在評論中閱讀更多內容“
http://pro-java.ru/java-dlya-opytnyx/obrabotchik-neperexvachennyx-isklyuchenij-java/ ”。此外,線程具有優先權。您可以在文章“
多線程中的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
。他們不應該混淆,因為… 如果我們直接運行該方法
run
,則不會啟動新執行緒。該方法
start
要求 JVM 建立一個新執行緒。具有 Thread 後代的選項是不好的,因為我們將 Thread 包含在類別層次結構中。第二個缺點是我們開始違反SOLID「單獨責任」的原則,因為 我們的類別同時負責管理執行緒和必須在該執行緒中執行的某些任務。哪個是對的?答案就在
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();
}
}
從Java 1.8開始它也是
Runnable
一個函數式介面。這使您可以更加漂亮地編寫線程的任務代碼:
public static void main(String []args){
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
全部的
因此,我希望從這個故事中可以清楚地了解什麼是流、它們如何存在以及可以用它們執行哪些基本操作。在下
一部分中,有必要了解線程如何相互交互以及它們的生命週期是什麼。#維亞切斯拉夫
GO TO FULL VERSION