溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java8容易遺忘的八個(gè)功能是什么

發(fā)布時(shí)間:2022-01-07 11:25:16 來源:億速云 閱讀:145 作者:iii 欄目:編程語言

本篇內(nèi)容介紹了“Java8容易遺忘的八個(gè)功能是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

Java的核心庫不斷加入各種復(fù)雜的用法來減少訪問共享資源時(shí)的線程等待時(shí)間。其中之一就是經(jīng)典的讀寫鎖(ReadWriteLock),它讓你把代碼分成兩部分:需要互斥的寫操作和不需要互斥的讀操作。

表面上看起來很不錯(cuò)。問題是讀寫鎖有可能是極慢的(最多10倍),這已經(jīng)和它的初衷相悖了。Java 8引入了一種新的讀寫鎖——叫做時(shí)間戳鎖。好消息是這個(gè)家伙真的非??臁南⑹撬褂闷饋砀鼜?fù)雜,有更多的狀態(tài)需要處理。并且它是不可重入的,這意味著一個(gè)線程有可能跟自己死鎖。

時(shí)間戳鎖有一種“樂觀”模式,在這種模式下每次加鎖操作都會(huì)返回一個(gè)時(shí)間戳作為某種權(quán)限憑證;每次解鎖操作都需要提供它對(duì)應(yīng)的時(shí)間戳。如果一個(gè)線程在請(qǐng)求一個(gè)寫操作鎖的時(shí)候,這個(gè)鎖碰巧已經(jīng)被一個(gè)讀操作持有,那么這個(gè)讀操作的解鎖將會(huì)失效(因?yàn)闀r(shí)間戳已經(jīng)失效)。這個(gè)時(shí)候應(yīng)用程序需要從頭再來,也許要使用悲觀模式的鎖(時(shí)間戳鎖也有實(shí)現(xiàn))。你需要自己搞定這一切,并且一個(gè)時(shí)間戳只能解鎖它對(duì)應(yīng)的鎖——這一點(diǎn)必須非常小心。

下面我們來看一下這種鎖的實(shí)例——

long stamp = lock.tryOptimisticRead(); // 非阻塞路徑——超級(jí)快  work(); // 我們希望不要有寫操作在這時(shí)發(fā)生  if (lock.validate(stamp)){  //成功!沒有寫操作干擾   }  else {  //肯定同時(shí)有另外一個(gè)線程獲得了寫操作鎖,改變了時(shí)間戳  //懶漢說——我們切換到開銷更大的鎖吧    stamp = lock.readLock(); //這是傳統(tǒng)的讀操作鎖,會(huì)阻塞  try {  //現(xiàn)在不可能有寫操作發(fā)生了  work();    }  finally {  lock.unlock(stamp); // 使用對(duì)應(yīng)的時(shí)間戳解鎖  }  }

并發(fā)加法器

Java 8另一個(gè)出色的功能是并發(fā)“加法器”,它對(duì)大規(guī)模運(yùn)行的代碼尤其有意義。一種最基本的并發(fā)模式就是對(duì)一個(gè)計(jì)數(shù)器的讀寫。就其本身而言,現(xiàn)今處理這個(gè)問題有很多方法,但是沒有一種能比Java 8提供的方法高效或優(yōu)雅。

到目前為止,這個(gè)問題是用原子類(Atomics)來解決的,它直接利用了CPU的“比較并交換”指令(CAS)來測(cè)試并設(shè)置計(jì)數(shù)器的值。問題在于當(dāng)一條CAS指令因?yàn)楦?jìng)爭(zhēng)而失敗的時(shí)候,AtomicInteger類會(huì)死等,在無限循環(huán)中不斷嘗試CAS指令,直到成功為止。在發(fā)生競(jìng)爭(zhēng)概率很高的環(huán)境中,這種實(shí)現(xiàn)被證明是非常慢的。

來看Java 8的LongAdder。這一系列類為大量并行讀寫數(shù)值的代碼提供了方便的解決辦法。使用超級(jí)簡(jiǎn)單。只要初始化一個(gè)LongAdder對(duì)象并使用它的add()和intValue()方法來累加和采樣計(jì)數(shù)器。

這和舊的Atomic類的區(qū)別在于,當(dāng)CAS指令因?yàn)楦?jìng)爭(zhēng)而失敗時(shí),Adder不會(huì)一直占著CPU,而是為當(dāng)前線程分配一個(gè)內(nèi)部cell對(duì)象來存儲(chǔ)計(jì)數(shù)器的增量。然后這個(gè)值和其他待處理的cell對(duì)象一起被加到intValue()的結(jié)果上。這減少了反復(fù)使用CAS指令或阻塞其他線程的可能性。

如果你問你自己,什么時(shí)候應(yīng)該用并發(fā)加法器而不是原子類來管理計(jì)數(shù)器?簡(jiǎn)單的答案就是——一直這么做。

并行排序

正像并發(fā)加法器能加速計(jì)數(shù)一樣,Java 8還實(shí)現(xiàn)了一種簡(jiǎn)潔的方法來加速排序。這個(gè)秘訣很簡(jiǎn)單。你不再這么做:

Array.sort(myArray);

而是這么做:

Arrays.parallelSort(myArray);

這會(huì)自動(dòng)把目標(biāo)數(shù)組分割成幾個(gè)部分,這些部分會(huì)被放到獨(dú)立的CPU核上去運(yùn)行,再把結(jié)果合并起來。這里需要注意的是,在一個(gè)大量使用多線程的環(huán)境中,比如一個(gè)繁忙的Web容器,這種方法的好處就會(huì)減弱(降低90%以上),因?yàn)樵絹碓蕉嗟腃PU上下文切換增加了開銷。

切換到新的日期接口

Java 8引入了全新的date-time接口。當(dāng)前接口的大多數(shù)方法都已被標(biāo)記為deprecated,你就知道是時(shí)候推出新接口了。新的日期接口為Java核心庫帶來了易用性和準(zhǔn)確性,而以前只能用Joda time才能達(dá)到這樣的效果(譯者注:Joda time是一個(gè)第三方的日期庫,比Java自帶的庫更友好更易于管理)。

跟任何新接口一樣,好消息是接口變得更優(yōu)雅更強(qiáng)大。但不幸的是還有大量的代碼在使用舊接口,這個(gè)短時(shí)間內(nèi)不會(huì)有改變。

為了銜接新舊接口,歷史悠久的Date類新增了toInstant()方法,用于把Date轉(zhuǎn)換成新的表示形式。當(dāng)你既要享受新接口帶來的好處,又要兼顧那些只接受舊的日期表示形式的接口時(shí),這個(gè)方法會(huì)顯得尤其高效。

控制操作系統(tǒng)進(jìn)程

想在你的代碼里啟動(dòng)一個(gè)操作系統(tǒng)進(jìn)程,通過JNI調(diào)用就能完成——但這個(gè)東西總令人一知半解,你很有可能得到一個(gè)意想不到的結(jié)果,并且一路伴隨著一些很糟糕的異常。

即便如此,這是無法避免的事情。但進(jìn)程還有一個(gè)討厭的特性就是——它們搞不好就會(huì)變成僵尸進(jìn)程。目前從Java中運(yùn)行進(jìn)程帶來的問題是,進(jìn)程一旦啟動(dòng)就很難去控制它。

為了幫我們解決這個(gè)問題,Java 8在Process類中引入了三個(gè)新的方法

  • destroyForcibly——結(jié)束一個(gè)進(jìn)程,成功率比以前高很多。

  • isAlive——查詢你啟動(dòng)的進(jìn)程是否還活著。

  • 重載了waitFor(),你現(xiàn)在可以指定等待進(jìn)程結(jié)束的時(shí)間了。進(jìn)程成功退出后這個(gè)接口會(huì)返回,超時(shí)的話也會(huì)返回,因?yàn)槟阌锌赡芤謩?dòng)終止它。

這里有兩個(gè)關(guān)于如何使用這些新方法的好例子——如果進(jìn)程沒有在規(guī)定時(shí)間內(nèi)退出,終止它并繼續(xù)往前走。

if (process.wait(MY_TIMEOUT, TimeUnit.MILLISECONDS)){ //成功  }else {     process.destroyForcibly(); }

在你的代碼結(jié)束前,確保所有的進(jìn)程都已退出。僵尸進(jìn)程會(huì)逐漸耗盡系統(tǒng)資源。

for (Process p : processes) { if (p.isAlive()) { p.destroyForcibly(); } }

精確的數(shù)字運(yùn)算

數(shù)字溢出會(huì)導(dǎo)致一些討厭的bug,因?yàn)樗举|(zhì)上不會(huì)出錯(cuò)。在一些系統(tǒng)中,整型值不停地增長(比如計(jì)數(shù)器),溢出的問題就尤為嚴(yán)重。在這些案例里面,產(chǎn)品在演進(jìn)階段運(yùn)行得很好,甚至商用后的很長時(shí)間內(nèi)也沒問題,但最終會(huì)出奇怪的故障,因?yàn)檫\(yùn)算開始溢出,產(chǎn)生了完全無法預(yù)料的值。

為了解決這個(gè)問題,Java 8為Math類添加了幾個(gè)新的“精確型”方法,以便保護(hù)重要的代碼不受溢出的影響,它的做法是當(dāng)運(yùn)算超過它的精度范圍的時(shí)候,拋出一個(gè)未檢查的ArithmeticException異常。

int safeC = Math.multiplyExact(bigA, bigB);  // 如果結(jié)果超出+-2^31,就會(huì)拋出ArithmeticException異常

不好的地方就是你必須自己找出可能產(chǎn)生溢出的代碼。無論如何,沒有什么自動(dòng)的解決方案。但我覺得有這些接口總比沒有好。

安全的隨機(jī)數(shù)發(fā)生器

在過去幾年中Java一直因?yàn)榘踩┒炊柺茉嵅 o論是否合理,Java已經(jīng)做了大量工作來加強(qiáng)虛擬機(jī)和框架層,使之免受攻擊。如果隨機(jī)數(shù)來源于隨機(jī)性不高的種子,那么那些用隨機(jī)數(shù)來產(chǎn)生密鑰或者散列敏感信息的系統(tǒng)就更易受攻擊。

到目前為止,隨機(jī)數(shù)發(fā)生算法由開發(fā)人員來決定。但問題是,如果你想要的算法依賴于特定的硬件、操作系統(tǒng)、虛擬機(jī),那你就不一定能實(shí)現(xiàn)它。這種情況下,應(yīng)用程序傾向于使用更弱的默認(rèn)發(fā)生器,這就使他們暴露在更大的風(fēng)險(xiǎn)下了。

Java 8添加了一個(gè)新的方法叫SecureRandom.getInstanceStrong(),它的目標(biāo)是讓虛擬機(jī)為你選擇一個(gè)安全的隨機(jī)數(shù)發(fā)生器。如果你的代碼無法完全掌控操作系統(tǒng)、硬件、虛擬機(jī)(如果你的程序部署到云或者PaaS上,這是很常見的),我建議你認(rèn)真考慮一下使用這個(gè)接口。

可選引用

空指針就像“踢到腳趾”一樣&mdash;&mdash;從你學(xué)會(huì)走路開始就伴隨著你,無論現(xiàn)在你有多聰明&mdash;&mdash;你還是會(huì)犯這個(gè)錯(cuò)。為了幫助解決這個(gè)老問題,Java 8引入了一個(gè)新模板叫Optional<T>。

這個(gè)模板是從Scala和Hashkell借鑒來的,用于明確聲明傳給函數(shù)或函數(shù)返回的引用有可能是空的。有了它,過度依賴舊文檔或者看過的代碼經(jīng)常變動(dòng)的人,就不需要去猜測(cè)某個(gè)引用是否可能為空。

Optional<User> tryFindUser(int userID) {

void processUser(User user, Optional<Cart> shoppingCart) {

Optional模板有一套函數(shù),使得采樣它更方便,比如isPresent()用來檢查這個(gè)值是不是非空,或者ifPresent()你可以傳遞一個(gè)Lambda函數(shù)過去,如果isPresent()返回true,這個(gè)Lambda函數(shù)就會(huì)被執(zhí)行。不好的地方就跟Java 8的新日期接口一樣,等這種模式逐漸流行,滲透到我們使用的庫中和日常設(shè)計(jì)中,需要時(shí)間和工作量。

value.ifPresent(System.out::print);

“Java8容易遺忘的八個(gè)功能是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI