你好!首先,祝賀您:您已經接觸到 Java 中的多線程主題!這是一項了不起的成就——您已經取得了長足的進步。但是請做好準備:這是本課程中最困難的主題之一。並不是說我們在這裡使用了複雜的類或許多方法:事實上,我們將使用不到 20 個。更重要的是你需要稍微改變一下你的想法。以前,您的程序是按順序執行的。代碼行有的來了,方法有的來了,基本就清楚了。首先,我們計算一些東西,然後在控制台上顯示結果,然後程序結束。要理解多線程,最好從並行性的角度來思考。讓我們從非常簡單的事情開始:) 想像一下您的家人正從一所房子搬到另一所房子。收集所有書籍將是此舉的重要部分。你積累了很多書,你需要把它們放在盒子裡。目前,您是唯一可用的。媽媽在做飯,弟弟在收拾衣服,妹妹去商店了。獨自一人,您可以以某種方式進行管理。遲早,你會自己完成任務,但這會花費很多時間。但是,您姐姐將在 20 分鐘後從商店回來,她沒有其他事情可做。所以她可以加入你。任務沒有改變:把書放進盒子裡。但它的執行速度是原來的兩倍。為什麼?因為工作是並行進行的。兩個不同的“線程”(你和你的妹妹)同時執行相同的任務。如果什麼都沒有改變,那麼和自己什麼都自己做的情況相比,時間上會有很大的差異。如果哥哥快點完成工作,他可以幫助你,事情會進行得更快。
假設 Thread-1 與某個 Object-1 交互,而 Thread-2 與 Object-2 交互。此外,該程序是這樣編寫的:
“Java Concurrency in Practice”寫於 2006 年,但並沒有失去它的相關性。它致力於多線程 Java 編程——從基礎知識到最常見的錯誤和反模式。如果有一天您決定成為多線程專家,那麼這本書是必讀的。 下節課見!:)
多線程解決的問題
發明多線程實際上是為了實現兩個重要目標:-
同時做幾件事。
在上面的示例中,不同的線程(家庭成員)並行執行多個操作:他們洗碗、去商店和收拾東西。
我們可以提供一個與編程更密切相關的例子。假設您有一個帶有用戶界面的程序。當您在程序中單擊“繼續”時,應該會進行一些計算,用戶應該會看到以下屏幕。如果按順序執行這些操作,則程序將在用戶單擊“繼續”按鈕後掛起。用戶將看到帶有“繼續”按鈕屏幕的屏幕,直到程序執行所有內部計算並到達刷新用戶界面的部分。
好吧,我想我們會等幾分鐘!
或者我們可以重新編寫我們的程序,或者像程序員所說的那樣,將其“並行化”。讓我們在一個線程上執行計算並在另一個線程上繪製用戶界面。大多數計算機都有足夠的資源來執行此操作。如果我們採用這條路線,那麼程序就不會凍結,用戶將在屏幕之間平穩移動,而不必擔心內部發生的事情。一個不干擾另一個:)
-
更快地執行計算。
這裡的一切都簡單得多。如果我們的處理器有多個內核,而今天的大多數處理器都有,那麼多個內核就可以並行處理我們的任務列表。顯然,如果我們需要執行 1000 個任務並且每個任務需要一秒鐘,那麼一個核心可以在 1000 秒內完成列表,兩個核心可以在 500 秒內完成,三個核心可以在 333 秒多一點,等等。
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("I'm Thread! My name is " + getName());
}
}
要創建和運行線程,我們需要創建一個類,讓它繼承java.lang。Thread類,並覆蓋其run()方法。最後一個要求非常重要。正是在run()方法中,我們定義了線程執行的邏輯。現在,如果我們創建並運行MyFirstThread的一個實例,run()方法將顯示帶有名稱的一行:getName()方法顯示線程的“系統”名稱,該名稱是自動分配的。但我們為什麼要試探性地說話呢?讓我們創建一個並找出答案!
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
控制台輸出: 我是線程!我的名字是 Thread-2 我是 Thread!我的名字是 Thread-1 我是 Thread!我的名字是 Thread-0 我是 Thread!我的名字是 Thread-3 我是 Thread!我的名字是 Thread-6 我是 Thread!我的名字是 Thread-7 我是 Thread!我的名字是 Thread-4 我是 Thread!我的名字是 Thread-5 我是 Thread!我的名字是 Thread-9 我是 Thread!我的名字是 Thread-8 讓我們創建 10 個線程(繼承Thread的MyFirstThread對象)並通過在每個對像上調用start()方法來啟動它們。調用start()方法後,會執行 run()方法中的邏輯。注意:線程名稱沒有順序。奇怪的是它們不是按順序排列的:、Thread-1、Thread-2等等?碰巧的是,這是一個不適合“順序”思維的時代的例子。問題是我們只提供了創建和運行 10 個線程的命令。線程調度器,一種特殊的操作系統機制,決定了它們的執行順序。它的精確設計和決策策略是我們現在不會深入討論的深入討論的主題。要記住的主要事情是程序員無法控制線程的執行順序。要了解情況的嚴重性,請嘗試多次運行上面示例中的 main() 方法。第二次運行時的控制台輸出: 我是線程!我的名字是 Thread-0 我是 Thread!我的名字是 Thread-4 我是 Thread!我的名字是 Thread-3 我是 Thread!我的名字是 Thread-2 我是 Thread!我的名字是 Thread-1 我是 Thread!我的名字是 Thread-5 我是 Thread!我的名字是 Thread-6 我是 Thread!我的名字是 Thread-8 我是 Thread!我的名字是 Thread-9 我是 Thread!我的名字是 Thread-7 第三次運行的控制台輸出: 我是 Thread!我的名字是 Thread-0 我是 Thread!我的名字是 Thread-3 我是 Thread!我的名字是 Thread-1 我是 Thread!我的名字是 Thread-2 我是 Thread!我的名字是 Thread-6 我是 Thread!我的名字是 Thread-4 我是 Thread!我的名字是 Thread-9 我是 Thread!我的名字是 Thread-5 我是 Thread!我的名字是 Thread-7 我是 Thread!我的名字是 Thread-8
多線程產生的問題
在我們的書籍示例中,您看到多線程解決了非常重要的任務並且可以使我們的程序更快。通常快很多倍。但是多線程被認為是一個困難的話題。事實上,如果使用不當,它會產生問題而不是解決問題。當我說“製造問題”時,我並不是指某種抽象意義上的問題。多線程可以產生兩個特定的問題:死鎖和競爭條件。死鎖是多個線程都在等待彼此佔有的資源,沒有一個可以繼續運行的情況。我們將在後續課程中詳細討論它。下面的示例現在就足夠了:
- Thread-1 停止與 Object-1 交互並切換到 Object-2,只要 Thread-2 停止與 Object-2 交互並切換到 Object-1。
- Thread-2 停止與 Object-2 交互並切換到 Object-1,只要 Thread-1 停止與 Object-1 交互並切換到 Object-2。
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
現在想像一下,該程序負責運行一個烹飪食物的機器人! Thread-0 從冰箱中取出雞蛋。Thread-1 打開爐子。Thread-2 拿了一個平底鍋放在爐子上。Thread-3 點燃爐子。Thread-4 將油倒入鍋中。Thread-5 打碎雞蛋並將它們倒入鍋中。Thread-6 將蛋殼扔進了垃圾桶。Thread-7 從燃燒器中取出煮熟的雞蛋。Thread-8 將煮熟的雞蛋放在盤子裡。Thread-9 洗碗。 看我們程序的執行結果: 線程執行:Thread-0 線程執行:Thread-2 線程執行 Thread-1 線程執行:Thread-4 線程執行:Thread-9 線程執行:Thread-5 線程執行:Thread-8 Thread執行:Thread-7 執行的線程:Thread-3 這是喜劇套路嗎?:) 這一切都是因為我們程序的工作取決於線程的執行順序。只要稍微違反規定的順序,我們的廚房就會變成地獄,一個瘋狂的機器人會摧毀周圍的一切。這也是多線程編程中的通病。你會不止一次聽到它。在結束本課時,我想推荐一本關於多線程的書。 
GO TO FULL VERSION