問(wèn)題:Java 提供了哪些 IO 方式? NIO 如何實(shí)現(xiàn)多路復(fù)用?
分析:
在實(shí)際面試中,從傳統(tǒng) IO 到 NIO、NIO 2,其中有很多地方可以擴(kuò)展開(kāi)來(lái),考察點(diǎn)涉及方方面面,比如:
基礎(chǔ) API 功能與設(shè)計(jì), InputStream/OutputStream 和 Reader/Writer 的關(guān)系和區(qū)別。
NIO、NIO 2 的基本組成。
給定場(chǎng)景,分別用不同模型實(shí)現(xiàn),分析 BIO、NIO 等模式的設(shè)計(jì)和實(shí)現(xiàn)原理。
NIO 提供的高性能數(shù)據(jù)操作方式是基于什么原理,如何使用?
或者,從開(kāi)發(fā)者的角度來(lái)看,你覺(jué)得 NIO 自身實(shí)現(xiàn)存在哪些問(wèn)題?有什么改進(jìn)的想法嗎?-
基礎(chǔ)回答:
Java IO 方式有很多種,基于不同的 IO 抽象模型和交互方式,可以進(jìn)行簡(jiǎn)單區(qū)分。首先,傳統(tǒng)的 java.io 包,它基于流模型實(shí)現(xiàn),提供了我們最熟知的一些 IO 功能,比如 File 抽象、輸入輸出流等。交互方式是同步、阻塞的方式,也就是說(shuō),在讀取輸入流或者寫(xiě)入輸出流時(shí),在讀、寫(xiě)動(dòng)作完成之前,線程會(huì)一直阻塞在那里,它們之間的調(diào)用是可靠的線性順序。
java.io 包的好處是代碼比較簡(jiǎn)單、直觀,缺點(diǎn)則是 IO 效率和擴(kuò)展性存在局限性,容易成為應(yīng)用性能的瓶頸。
很多時(shí)候,人們也把 java.net 下面提供的部分網(wǎng)絡(luò) API,比如 Socket、ServerSocket、HttpURLConnection 也歸類(lèi)到同步阻塞 IO 類(lèi)庫(kù),因?yàn)榫W(wǎng)絡(luò)通信同樣是 IO 行為。第二,在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel、Selector、Buffer 等新的抽象,可以構(gòu)建多路復(fù)用的、同步非阻塞 IO 程序,同時(shí)提供了更接近操作系統(tǒng)底層的高性能數(shù)據(jù)操作方式。
第三,在 Java 7 中,NIO 有了進(jìn)一步的改進(jìn),也就是 NIO 2,引入了異步非阻塞 IO 方式,也有很多人叫它 AIO(Asynchronous IO)。異步 IO 操作基于事件和回調(diào)機(jī)制,可以簡(jiǎn)單理解為,應(yīng)用操作直接返回,而不會(huì)阻塞在那里,當(dāng)后臺(tái)處理完成,操作系統(tǒng)會(huì)通知相應(yīng)線程進(jìn)行后續(xù)工作。
問(wèn)題:如何保證容器是線程安全的?ConcurrentHashMap 如何實(shí)現(xiàn)高效地線程安全?
-
分析:
不管大小廠面試,對(duì)線程及線程安全一定是避不開(kāi)的話題?,F(xiàn)在的套路一般就從基本的線程并發(fā)情況下會(huì)出什么問(wèn)題--->線程并發(fā)問(wèn)安全問(wèn)題如何解決---->常用的concurrent包組件---->常用的線程安全集合---->線程安全集合的實(shí)現(xiàn)原理這一套問(wèn)題。
在面試準(zhǔn)備過(guò)程中,concurrent包里面的常見(jiàn)組件,如線程池,Atomic,Concurrent這幾套東西,是一定要好好準(zhǔn)備。如果要深入思考并回答這個(gè)問(wèn)題及其擴(kuò)展方面,至少需要:
- 理解基本的線程安全工具。
- 理解傳統(tǒng)集合框架并發(fā)編程中 Map 存在的問(wèn)題,清楚簡(jiǎn)單同步方式的不足。
- 梳理并發(fā)包內(nèi),尤其是 ConcurrentHashMap 采取了哪些方法來(lái)提高并發(fā)表現(xiàn)。
- 最好能夠掌握 ConcurrentHashMap 自身的演進(jìn),目前的很多分析資料還是基于其早期版本。
-
基本回答:
Java 提供了不同層面的線程安全支持。在傳統(tǒng)集合框架內(nèi)部,除了 Hashtable 等同步容器,還提供了所謂的同步包裝器(Synchronized Wrapper),我們可以調(diào)用 Collections 工具類(lèi)提供的包裝方法,來(lái)獲取一個(gè)同步的包裝容器(如 Collections.synchronizedMap),但是它們都是利用非常粗粒度的同步方式,在高并發(fā)情況下,性能比較低下。
另外,更加普遍的選擇是利用并發(fā)包提供的線程安全容器類(lèi),它提供了:各種并發(fā)容器,比如 ConcurrentHashMap、CopyOnWriteArrayList。
各種線程安全隊(duì)列(Queue/Deque),如 ArrayBlockingQueue、SynchronousQueue。
各種有序容器的線程安全版本等。
具體保證線程安全的方式,包括有從簡(jiǎn)單的 synchronize 方式,到基于更加精細(xì)化的,比如基于分離鎖實(shí)現(xiàn)的 ConcurrentHashMap 等并發(fā)實(shí)現(xiàn)等。具體選擇要看開(kāi)發(fā)的場(chǎng)景需求,總體來(lái)說(shuō),并發(fā)包內(nèi)提供的容器通用場(chǎng)景,遠(yuǎn)優(yōu)于早期的簡(jiǎn)單同步實(shí)現(xiàn)。
其次,ConcurrentHashMap 的設(shè)計(jì)實(shí)現(xiàn)其實(shí)一直在演化,比如在 Java 8 中就發(fā)生了非常大的變化(Java 7 其實(shí)也有不少更新)這里先簡(jiǎn)單說(shuō)明,早期 ConcurrentHashMap,其實(shí)現(xiàn)是基于:
- 分離鎖,也就是將內(nèi)部進(jìn)行分段(Segment),里面則是 HashEntry 的數(shù)組,和 HashMap 類(lèi)似,哈希相同的條目也是以鏈表形式存放。
- HashEntry 內(nèi)部使用 volatile 的 value 字段來(lái)保證可見(jiàn)性,也利用了不可變對(duì)象的機(jī)制以改進(jìn)利用 Unsafe 提供的底層能力,比如 volatile access,去直接完成部分操作,以最優(yōu)化性能,畢竟 Unsafe 中的很多操作都是 JVM intrinsic 優(yōu)化過(guò)的。
問(wèn)題:synchronized 和 ReentrantLock 有什么區(qū)別?有人說(shuō) synchronized 最慢,這話靠譜嗎?
-
分析:題目是考察并發(fā)編程的常見(jiàn)基礎(chǔ)題,下面給出的典型回答算是一個(gè)相對(duì)全面的總結(jié)。
對(duì)于并發(fā)編程,不同公司或者面試官面試風(fēng)格也不一樣,有個(gè)別大廠喜歡一直追問(wèn)你相關(guān)機(jī)制的擴(kuò)展或者底層,有的喜歡從實(shí)用角度出發(fā),所以你在準(zhǔn)備并發(fā)編程方面需要一定的耐心。
個(gè)人認(rèn)為,鎖作為并發(fā)的基礎(chǔ)工具之一,你至少需要掌握:- 理解什么是線程安全。
- synchronized、ReentrantLock 等機(jī)制的基本使用與案例。
更進(jìn)一步,你還需要: - 掌握 synchronized、ReentrantLock 底層實(shí)現(xiàn);理解鎖膨脹、降級(jí);理解偏斜鎖、自旋鎖、輕量級(jí)鎖、重量級(jí)鎖等概念。
- 掌握并發(fā)包中 java.util.concurrent.lock 各種不同實(shí)現(xiàn)和案例分析。
-
基本回答:
synchronized 是 Java 內(nèi)建的同步機(jī)制,所以也有人稱(chēng)其為 Intrinsic Locking,它提供了互斥的語(yǔ)義和可見(jiàn)性,當(dāng)一個(gè)線程已經(jīng)獲取當(dāng)前鎖時(shí),其他試圖獲取的線程只能等待或者阻塞在那里。在 Java 5 以前,synchronized 是僅有的同步手段,在代碼中, synchronized 可以用來(lái)修飾方法,也可以使用在特定的代碼塊兒上,本質(zhì)上 synchronized 方法等同于把方法全部語(yǔ)句用 synchronized 塊包起來(lái)。
ReentrantLock,通常翻譯為再入鎖,是 Java 5 提供的鎖實(shí)現(xiàn),它的語(yǔ)義和 synchronized 基本相同。再入鎖通過(guò)代碼直接調(diào)用 lock() 方法獲取,代碼書(shū)寫(xiě)也更加靈活。與此同時(shí),ReentrantLock 提供了很多實(shí)用的方法,能夠?qū)崿F(xiàn)很多 synchronized 無(wú)法做到的細(xì)節(jié)控制,比如可以控制 fairness,也就是公平性,或者利用定義條件等。但是,編碼中也需要注意,必須要明確調(diào)用 unlock() 方法釋放,不然就會(huì)一直持有該鎖。
synchronized 和 ReentrantLock 的性能不能一概而論,早期版本 synchronized 在很多場(chǎng)景下性能相差較大,在后續(xù)版本進(jìn)行了較多改進(jìn),在低競(jìng)爭(zhēng)場(chǎng)景中表現(xiàn)可能優(yōu)于 ReentrantLock。
推薦資源:
Java基礎(chǔ)教學(xué)視頻,https://ke.qq.com/course/272077和https://ke.qq.com/course/326275