歡迎來到《并發(fā)王者課》降瞳,本文是該系列文章中的第1篇征讲。
從本文開始锅知,我將基于王者中的段位和場景蛛芥,從青銅商膊、黃金、鉑金帕翻、磚石鸠补、星耀到王者,不同的段位對應(yīng)不同的難易程度嘀掸,由淺入深逐步介紹JAVA中的并發(fā)編程紫岩,并在每周二、四睬塌、六持續(xù)更新泉蝌。
在文章的知識體系方面,主要以實踐為主揩晴,并在實踐中穿插理論知識的講解勋陪,而本文將從最簡單的線程創(chuàng)建開始。
一文狱、一個游戲場景
在本局游戲中粥鞋,將有3位玩家出場,他們分別是哪吒瞄崇、蘇烈和安其拉呻粹。根據(jù)玩家不同的角色定位,在王者峽谷中苏研,他們會有不同的游戲路線:
- 作為戰(zhàn)士的哪吒將走上路的對抗路線等浊;
- 法師安其拉則去鎮(zhèn)守中路;
- 戰(zhàn)坦蘇烈則決定去下路摹蘑。
二筹燕、代碼實現(xiàn)
顯而易見,你已經(jīng)發(fā)現(xiàn)這幾個玩家肯定不是單線程衅鹿。接下來撒踪,我們將通過簡單的多線程模擬出他們的路線。當(dāng)然大渤,真實的游戲引擎中絕不會是幾個簡單的線程制妄,情況會復(fù)雜很多。
public static void main(String[] args) {
Thread neZhaPlayer = new Thread() {
public void run() {
System.out.println("我是哪吒泵三,我去上路");
}
};
Thread anQiLaPlayer = new Thread() {
public void run() {
System.out.println("我是安其拉耕捞,我去中路");
}
};
Thread suLiePlayer = new Thread() {
public void run() {
System.out.println("我是蘇烈,我去下路");
}
};
neZhaPlayer.start();
anQiLaPlayer.start();
suLiePlayer.start();
}
代碼的運行結(jié)果:
我是哪吒烫幕,我去上路
我是蘇烈俺抽,我去下路
我是安其拉,我去中路
Process finished with exit code 0
以上较曼,就是游戲中簡單的代碼片段磷斧。我們創(chuàng)建了3個線程表示3個玩家,并通過run()
方法實現(xiàn)他們的路線動作捷犹,隨后通過start()
啟動線程弛饭。它足夠簡單,然而這里有3個知識點需要你留意伏恐。
1. 創(chuàng)建線程
Thread neZhaPlayer = new Thread();
2. 執(zhí)行代碼片段
public void run() {
System.out.println("我是哪吒孩哑,我去上路");
}
3. 啟動線程
neZhaPlayer.start();
對于我們來說,創(chuàng)建線程并不是我們的目標(biāo)翠桦,我們的目標(biāo)是運行我們期望的代碼(比如玩家的游戲路線或某個動作)横蜒,而線程只是我們實現(xiàn)這一目標(biāo)的方式。因此销凑,在編寫多線程代碼時丛晌,運行指定的代碼片段無疑是極其重要的。在Java中斗幼,我們主要有2種方式來指定:
- 繼承
Thread
并重寫run
方法澎蛛; - 實現(xiàn)
Runnable
接口并將其傳遞給Thread
的構(gòu)造器。
三蜕窿、線程創(chuàng)建的兩種方式
1. 繼承Thread創(chuàng)建線程
在上面的示例代碼中谋逻,我們所使用的正是這種方式呆馁,只不過是匿名實現(xiàn),你也可以通過顯示繼承:
public class NeZhaPlayer extends Thread {
public void run() {
System.out.println("我是哪吒毁兆,我去上路");
}
}
此外浙滤,在Java以及更高的JDK版本中,你還可以通過lambda表達式簡化代碼:
Thread anQiLaPlayer = new Thread(() -> System.out.println("我是哪吒气堕,我去上路"));
2. 實現(xiàn)Runnable接口創(chuàng)建線程
創(chuàng)建線程的第2種方法是實現(xiàn)Runnable
接口纺腊。我們創(chuàng)建了NeZhaRunnable
類并實現(xiàn)Runnable
接口中的run
方法,如下面代碼所示茎芭。
public class NeZhaRunnable implements Runnable {
public void run() {
System.out.println("我是哪吒揖膜,我去上路");
}
}
Thread neZhaPlayer = new Thread(new NeZhaRunnable());
neZhaPlayer.start();
從效果上看,兩種方式創(chuàng)建出來的線程效果是一樣的梅桩。那么壹粟,我們應(yīng)該怎么選擇?
建議你使用Runnable
對于這兩種方法摘投,孰優(yōu)孰劣并沒有明確的規(guī)定煮寡。但是,從面向?qū)ο笤O(shè)計的角度來說犀呼,推薦你用第二種方式:實現(xiàn)Runnable接口幸撕。
這是因為,在面向?qū)ο笤O(shè)計中外臂,有一條約定俗成的規(guī)則坐儿,組合優(yōu)于繼承(Prefer composition over inheritance),如果沒有特別的目的需要重寫父類方法宋光,盡量不要使用繼承貌矿。在Java中所有的類都只能是單繼承,一旦繼承Thread之后將不能繼承其他類罪佳,嚴重影響類的擴展和靈活性逛漫。另外,實現(xiàn)Runnable接口也可以與后面的更高級的并發(fā)工具結(jié)合使用赘艳。
所以酌毡,相較于繼承Thread,實現(xiàn)Runnable接口可以降低代碼之間的耦合蕾管,保持更好的靈活性枷踏。關(guān)于這一原則的更多描述,你可以參考《Effective Java》掰曾。
當(dāng)然旭蠕,如果你對Thread情有獨鐘,當(dāng)我沒說。此外掏熬,在Java中我們還可以通過ThreadFactory
等工具類創(chuàng)建線程佑稠,不過本質(zhì)上仍是對這兩種方法的封裝。
四孽江、注意讶坯,別踩坑番电!
線程的啟動固然簡單岗屏,然而對于一些新手來說,在啟動線程的時候漱办,一不小心就會使用run()
而不是start()
这刷,就像下面這樣:
Thread neZhaPlayer = new Thread(new NeZhaRunnable());
neZhaPlayer.run();
如果你這么調(diào)用的話,你仍然可以看到你期望的輸出娩井,然而這正是陷進所在暇屋!這是因為,Runnable中的run()
方法并不是你所創(chuàng)建的線程調(diào)用的洞辣,而是調(diào)用你這個線程的線程調(diào)用的咐刨,也就是主線程。那為什么直接調(diào)用run()
方法也能看到輸出呢扬霜?這是因為Thread中的run()
會直接調(diào)用target中的run()
:
public void run() {
if (target != null) {
target.run();
}
}
所以你看定鸟,如果你直接調(diào)用run()
的話,并不會創(chuàng)建新的線程著瓶。關(guān)于這兩個方法的執(zhí)行細節(jié)联予,會在后面的線程狀態(tài)中分析,這里你要記住的就是啟動線程調(diào)用的是start()
材原,而不是run()
沸久。
以上就是文本的全部內(nèi)容,恭喜你又上了一顆星?
夫子的試煉
用兩種不同的方式余蟹,創(chuàng)建出兩個線程卷胯,交差打印1~100之間的奇數(shù)和偶數(shù),并斷點調(diào)試威酒。
關(guān)于作者
關(guān)注公眾號【技術(shù)八點半】窑睁,及時獲取文章更新昂拂。傳遞有品質(zhì)的技術(shù)文章汗销,記錄平凡人的成長故事,偶爾也聊聊生活和理想榴芳。早晨8:30推送作者品質(zhì)原創(chuàng)佛呻,晚上20:30推送行業(yè)深度好文裳朋。
如果本文對你有幫助,歡迎點贊、關(guān)注鲤嫡、監(jiān)督送挑,我們一起從青銅到王者。