- Java高并發(fā)與集合框架:JCF和JUC源碼分析與實現(xiàn)
- 銀文杰
- 4852字
- 2022-08-16 17:28:42
前言
1. 寫在開篇
為什么撰寫本書
筆者一直在信息系統(tǒng)建設(shè)的一線工作,承擔過多種不同的工作職責。在工作中,筆者經(jīng)常和掌握不同技術(shù)的朋友討論具體問題的解決方案,發(fā)現(xiàn)在Java體系中,大家使用最多的是Java集合框架(JCF)和Java并發(fā)工具包(JUC)。實際上,JCF和JUC已經(jīng)能夠覆蓋筆者及朋友們工作中遇到的超過8成的應(yīng)用場景,但是大家往往無法快速匹配最合適的技術(shù)方案。此外,在JCF和JUC中存在大量可以在實際工作中借鑒的設(shè)計方案,雖然網(wǎng)絡(luò)上有一些零散的關(guān)于集合的介紹,但深入講解其工作原理的內(nèi)容并不多,甚至有一些資料存在質(zhì)量問題。
筆者“擼碼”近17年,慶幸頭未禿、智未衰。于是筆者在1年前產(chǎn)生了這樣的想法,希望將自己在實際工作中梳理、總結(jié)的JCF、JUC的相關(guān)知識成體系地介紹給大家,也希望將自己在閱讀JDK源碼(包括JCF、JUC、I/O、NET等模塊)后總結(jié)和思考的可用于實際工作的技術(shù)手段成體系地分享給大家。
有了想法,便著手行動,經(jīng)過大半年的整理、撰寫、調(diào)整,終成本書。因個人水平有限,書中難免有錯誤和疏漏之處,希望各位讀者能誠意指出、不吝賜教。此外,各位讀者可以直接通過筆者的郵箱yin-wen-jie@163.com聯(lián)系筆者本人。
關(guān)于本書選擇的JDK版本
由于JDK版本迭代速度較快,本書的整理和撰寫又需要一個較長的時間,并且書中內(nèi)容包括大量源碼分析和講解,因此本書首先要解決的問題,就是選定一個本書進行源碼講解和分析所依據(jù)的JDK版本。
這個問題需要從JDK版本的更新特點、大家使用JDK版本的習(xí)慣和撰寫本書的目的來考慮,目前大家在實際工作中使用最多的版本是JDK 1.8和JDK 11,并且出于對Oracle商業(yè)運作的考慮,JDK 11之后的升級版本已不再免費提供,因此大家在生產(chǎn)環(huán)境中一般使用Open JDK作為運行環(huán)境。同樣出于對Oracle商業(yè)運作的考慮,JDK版本的發(fā)布周期固定為每半年發(fā)布一個大版本、每3年發(fā)布一個LTS/LMS版本(長期支持版本),截至2021年4月,JDK 16已經(jīng)發(fā)布(JDK 16是一個短期過渡版本),緊接著2021年9月,JDK 17正式發(fā)布(這是一個LTS/LMS版本),這顯然加大了讀者學(xué)習(xí)和篩選JDK版本的工作量。
好消息是Oracle基本開源了所有已成熟的JDK版本,是否商業(yè)化運行并不會影響我們對這些JDK源碼的學(xué)習(xí)(只要不用于商業(yè)用途),而且JDK版本的向下兼容性保證了讀者在了解JDK的工作原理后,可以將其應(yīng)用到自己正在使用的JDK版本上。此外,越新的JDK版本對關(guān)鍵數(shù)據(jù)結(jié)構(gòu)、關(guān)鍵算法實現(xiàn)過程的優(yōu)化越多,本書希望在講解過程中,可以盡可能多地將這些有益的優(yōu)化點介紹給讀者。
在綜合考慮各因素后,本書將JDK 14作為本書講解源碼依據(jù)的主要版本(在后續(xù)內(nèi)容中,如果不特別說明,那么代碼分析都是基于JDK 14進行的)。JDK 14是在整理、編寫本書時發(fā)布的版本。在該版本中,與本書主要內(nèi)容有關(guān)的數(shù)據(jù)結(jié)構(gòu)、核心算法、設(shè)計方案和之前的版本基本保持穩(wěn)定和兼容,便于讀者在常用的JDK 1.8、JDK 11中找到對應(yīng)的實現(xiàn)位置。本書介紹的JDK 14中的源碼內(nèi)容是完全開源的,讀者可以在Oracle官方網(wǎng)站直接下載這些源碼。
本書的目標讀者
本書前半部分可以作為Java編程的入門學(xué)習(xí)內(nèi)容,也可以作為初學(xué)者進行JCF部分知識查漏補缺的參考資料;本書后半部分對基礎(chǔ)知識有一定的要求,適合有一些Java編程基礎(chǔ)的程序員閱讀。此外,本書可以作為程序員對JCF部分和JUC部分知識結(jié)構(gòu)進行梳理的參考用書。
2. 本書約定
1)關(guān)于源碼注釋及代碼格式。
本書中有大量基于JDK 14的源碼片段。筆者會對這些源碼片段逐段說明、逐條分析,讀者不用擔心無法讀懂這些源碼。此外,大部分章節(jié)在對源碼進行分析后,會使用圖文方式對源碼中的重要知識點進行歸納和總結(jié)。
引用大量的源碼會占用篇幅。為了盡可能節(jié)約紙張,本書中的示例代碼沒有遵循Java官方推薦的注釋規(guī)范和代碼格式規(guī)范,本書會對代碼和注釋進行格式壓縮。本書主要采用以下兩種格式壓縮方式。
? 采用單行注釋代替多行注釋。
Java官方推薦的多行注釋方式采用的是“/***/”,如下所示。

這種注釋方式非常清晰且容易閱讀,但是占用了過長的篇幅,所以本書會將上述注釋轉(zhuǎn)換為單行注釋,如下所示。

? 采用壓縮格式替換單行代碼塊。
如果代碼塊中只有一行代碼,那么Java允許省略代碼塊中的大括號“{}”。例如,在if語句的代碼塊中,如果只有一行執(zhí)行代碼,則可以采用如下方式進行書寫。

但這種書寫方式容易在排布緊湊的局部位置引起閱讀障礙,所以針對源碼中的這種簡寫方式,本書進行了簡寫還原和格式壓縮。書中會恢復(fù)所有被簡寫的代碼段落的大括號“{}”,從而方便對源碼進行分析,并且將只有一行代碼的代碼塊壓縮成單行,如下所示。

2)關(guān)于JDK版本的命名。
JDK 1.2~JDK 1.8都采用1.X格式的小版本號,但是在JDK 1.8后,Oracle改為采用大版本號對其進行命名,如JDK 9、JDK 11等。本書也會采用這種命名方式,但是由于各個版本功能存在差異,因此為了表達從某個JDK版本開始支持某種功能或特性,本書會采用“+”符號表示。例如,如果要表達從JDK 1.8開始支持某種特性,則用JDK 1.8+表示;如果要表達從JDK 11開始支持某種特性,則用JDK 11+表示。
3)其他約定。
? 關(guān)于JVM的稱呼約定。
本書無意深入分析JVM的內(nèi)部運行原理,也不會深入討論JVM每個模塊負責的具體工作。例如,本書不會分析JIT(即時編譯器)指令重排的細節(jié),以及在什么情況下代碼指令不會被編譯執(zhí)行,而會被解釋執(zhí)行。凡是涉及內(nèi)部運行原理的內(nèi)容,本書將其統(tǒng)稱為JVM運行過程。此外,如果沒有特別說明,那么本書提到的JVM都表示HotSpot版本的虛擬機。
? 關(guān)于方法的稱呼約定。
由于Java中的方法涉及多態(tài)場景,因此本書需要保證對Java中方法的稱呼不出現(xiàn)二義性。例如,java.lang.Object類中的wait()方法存在多態(tài)表達,代碼如下。

在不產(chǎn)生二義性的情況下,本書會直接采用“wait()方法”的描述方式。如果需要介紹多態(tài)場景中方法名相同、入?yún)⒉煌姆椒ū磉_的不同工作特性,那么不加區(qū)別就會造成二義性,這時本書會采用“wait()”“wait(long)”分別進行特定描述。
? 關(guān)于圖表的約定。
本書主要采用圖文方式對Java源碼進行說明、分析和總結(jié),由于客觀限制,大量的插圖只能采用黑白方式呈現(xiàn),因此如果有需要,則會在插圖后的正文中或插圖右上角給出圖例說明。
? 關(guān)于System.out對象的使用。
在實際工作中,推薦使用slf4j-log4j方式進行日志/控制臺輸出,但本書中的代碼片段大量使用System.out對象進行控制臺輸出,這并不影響讀者理解這些代碼片段的邏輯,也有利于不同知識水平的讀者將精力集中在理解核心思路上。
? 關(guān)于包簡寫的約定。
本書大部分內(nèi)容涉及Java集合框架(JCF)和Java并發(fā)工具包(JUC),JCF和JUC通常涉及較長的包路徑。例如,在JUC中,封裝后最終向程序員開放的原子性操作工具類位于java.util.concurrent.atomic包下。如果本書中每一個類的全稱都攜帶這么長的包路徑,那么顯然是沒必要的。為了節(jié)約篇幅,本書會使用包路徑下每個路徑點的首字母對包路徑進行簡寫,如將“java.util.concurrent.atomic”包簡寫為j.u.c.atomic包。
? 關(guān)于集合、集合對象、隊列的約定。
讀者應(yīng)該都已經(jīng)知曉,對象是類的實例。本書將JCF中的具體類稱為集合,將JCF中類的實例對象稱為集合對象。隊列是一種具有特定工作效果的集合,從繼承結(jié)構(gòu)上來說,本書會將JCF中實現(xiàn)了java.util.Queue接口的集合稱為隊列。這主要是為了表述方便,并不代表筆者認為集合、集合對象和隊列在JVM工作原理層面上有任何差異。
3. 必要的前置知識
本書難度適中,但仍然需要讀者對Java編程語言具備基本認知,這樣才能通暢地閱讀本書所有內(nèi)容。這種基本認知與工作年限沒有關(guān)系,屬于只要是Java程序員就應(yīng)該掌握的知識。
1)關(guān)于位運算的知識。
Java支持基于二進制的位運算操作。在Java中,使用“>>”表示無符號位的右移運算,使用“>>>”表示有符號位的右移運算,使用“<<”表示無符號位的左移運算,使用“<<<”表示有符號位的左移運算。
在Java中,基于整數(shù)的位運算相當于整數(shù)的乘法運算或除法運算。如“x>>1”表示將x除以2,“x<<1”表示將x乘以2。在JCF中,無論是哪個版本的Java源碼,都會采用位運算來實現(xiàn)整數(shù)與2的乘法運算或除法運算。此外,讀者需要知道如何對某個負整數(shù)進行二進制表達。在特定情況下,Java使用與運算替換取余運算。例如,通過語句“x&255”可實現(xiàn)取余運算,這句代碼的意義為對256取余。
2)關(guān)于對象引用、引用傳遞和“相等”的知識。
Java中有八種基礎(chǔ)類型和類類型,可以使用引用的方式給類的對象賦值。在調(diào)用方法時,除八種基礎(chǔ)類型的變量外,傳遞的都是對象引用(包括基于基礎(chǔ)類型的數(shù)組對象)。
Java中的“相等”有兩種含義,一種是值相等,另一種是引用地址相等。值相等是由對象中的equals()方法和hashCode()方法配合實現(xiàn)的(如果兩個對象的值相等,那么使用equals()方法對這兩個對象進行比較會返回true,并且使用hashCode()方法對這兩個對象進行比較返回的int類型的值也必然相同);引用地址相等是由兩個對象(注意:不是基本類型數(shù)據(jù))使用“==”運算符進行比較得到的。
本書雖然未涉及動態(tài)常量池、字符串常量池的相關(guān)知識,但需要讀者知曉這些,否則無法理解類似于“String”的字符串對象或基礎(chǔ)類型裝箱后的對象關(guān)于“相等”的工作原理。
3)關(guān)于對象序列化和反序列化的知識。
Java的序列化和反序列化過程主要是指由java.io包支持的將對象轉(zhuǎn)換為字節(jié)序列并輸出的過程和將字節(jié)序列轉(zhuǎn)換為對象并輸入的過程。JCF中的大部分集合都對對象的序列化和反序列化過程進行了重新實現(xiàn)。其中要解決的問題有很多,包括提高各種集合在序列化和反序列化過程中的性能問題(這個很關(guān)鍵,因為集合中通常存儲了大量數(shù)據(jù)),以及如何保證集合在不同JDK版本中進行反序列化時的兼容性問題。
本書不會專門講解每一種集合在序列化和反序列化過程中的工作細節(jié),以及如何解決上述問題,并且默認讀者知曉Java中的對象可以使用writeObject(ObjectOutputStream)方法和readObject(ObjectInputStream)方法對序列化和反序列化過程進行干預(yù)。
4)關(guān)于線程的知識。
為了介紹在高并發(fā)場景中工作的集合,本書會先介紹Java并發(fā)工具包(JUC)中的相關(guān)知識點,所以讀者需要知道Java中的基本線程使用方法,如如何創(chuàng)建和運行一個用戶線程。
5)關(guān)于原子性操作的基礎(chǔ)知識。
在閱讀本書前,讀者無須知道引起原子性操作問題的底層原因,但需要知道Java并不能保證所有場景中的原子性操作。例如,在多線程情況下,如果沒有施加任何安全措施,那么Java無法保證類似于“i++”語句的原子性。
4. 本書的知識結(jié)構(gòu)和脈絡(luò)
由于JCF和JUC有非常龐大的知識體系,因此無法用有限的篇幅覆蓋所有知識點。例如,本書并未講解JCF中的每種集合對fail-fast機制的匹配設(shè)計,也沒有講解每種集合對對象序列化和反序列化的優(yōu)化設(shè)計。此外,即使要講解指定范圍內(nèi)的知識點,也需要有清晰的思路和知識脈絡(luò),從而幫助讀者更好地理解。因此,在正式閱讀本書內(nèi)容前,需要了解本書內(nèi)容的介紹路徑。
首先,本書會介紹最基礎(chǔ)的集合,它們都屬于JCF的知識范疇,分別屬于List、Queue、Map和Set性質(zhì)的集合,并且與JUC不存在使用場景交集。在這一部分,本書會介紹這些集合的基本工作原理(這些集合的外在功能表現(xiàn)各不相同,但它們的內(nèi)在數(shù)據(jù)結(jié)構(gòu)具備共性),并且選擇其中的重要集合及其數(shù)據(jù)結(jié)構(gòu)來詳細講解。
然后,本書會向在高并發(fā)場景中工作的集合進行過渡,在這一部分,本書的內(nèi)容難度有所提升,所以在正式介紹這些集合前,會先介紹與之有關(guān)的高并發(fā)知識。例如,在高并發(fā)場景中如何保證原子性、可見性和有序性,Java中兩種管程的工作原理和使用方法,Java為什么需要通過自行實現(xiàn)的管程技術(shù)解決多線程問題。實際上,這些都是JUC的相關(guān)知識,如圖0-1所示。

圖0-1
最后,本書會介紹在高并發(fā)場景中使用的集合,這些集合主要負責兩類任務(wù),一類任務(wù)是在高并發(fā)場景中正確完成數(shù)據(jù)的存儲工作并為多個線程分享數(shù)據(jù),另一類任務(wù)是在高并發(fā)場景中主導(dǎo)消費者線程(從集合中讀取數(shù)據(jù)的線程)和生產(chǎn)者線程(向集合中寫入數(shù)據(jù)的線程)之間的數(shù)據(jù)傳輸工作。這部分主要介紹Queue/Deque集合,以及它們是如何在保證線程安全性的前提下,利用各種設(shè)計技巧提高工作效率的。
根據(jù)本書的知識邏輯,讀者會從JCF部分的知識脈絡(luò)過渡到JUC部分的知識脈絡(luò),最后回到JCF本身,其中涉及的集合和數(shù)據(jù)結(jié)構(gòu)如圖0-2所示。需要注意的是,本書不會對圖0-2中的所有集合和數(shù)據(jù)結(jié)構(gòu)進行詳細講解,而是有選擇地進行詳細講解。例如,HashMap集合具有代表性,所以會對其進行詳細講解,然后在此基礎(chǔ)上說明LinkedHashMap集合是如何對前者進行結(jié)構(gòu)擴展的;對于HashSet集合,雖然該集合經(jīng)常在工作中使用,但其工作原理依賴于HashMap集合,所以只會對其進行粗略講解;為了讓讀者清晰理解那些適合工作在高并發(fā)場景中的集合是如何工作的,本書除了介紹Java中的兩種管程技術(shù)、多線程中的三性問題等知識,還會詳細介紹具有工作共性的數(shù)據(jù)結(jié)構(gòu)(如堆、紅黑樹、數(shù)組、鏈表等),使讀者能在高并發(fā)場景中結(jié)合數(shù)據(jù)結(jié)構(gòu)進行思考和理解。

圖0-2
- 少兒人工智能趣味入門:Scratch 3.0動畫與游戲編程
- 華為HMS生態(tài)與應(yīng)用開發(fā)實戰(zhàn)
- OpenCV for Secret Agents
- 用戶體驗增長:數(shù)字化·智能化·綠色化
- C語言程序設(shè)計
- PySpark Cookbook
- 軟件測試實用教程
- Python機器學(xué)習(xí):預(yù)測分析核心算法
- Node.js開發(fā)指南
- Learning iOS Security
- Magento 2 Beginners Guide
- 從零開始學(xué)Python大數(shù)據(jù)與量化交易
- Data Manipulation with R(Second Edition)
- HTML5 WebSocket權(quán)威指南
- Developing Java Applications with Spring and Spring Boot