CodeGym /Java Blog /Toto sisi /更好的結合:Java 和 Thread 類。第一部分——執行線程
John Squirrels
等級 41
San Francisco

更好的結合:Java 和 Thread 類。第一部分——執行線程

在 Toto sisi 群組發布

介紹

多線程從一開始就內置在 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 和 Thread 類。 第一部分 — 執行線程 - 5

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 類。第六部分——開火!
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION