偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

阿里專(zhuān)家與你分享:你必須了解的Java多線程技術(shù)!

開(kāi)發(fā) 后端
Lambda起源于數(shù)學(xué)中的λ演算中的一個(gè)匿名函數(shù),從它的起源我們可以知道,Lambda本身就是一個(gè)匿名函數(shù),是Java8才推出的亮點(diǎn),體現(xiàn)了函數(shù)式編程的思想?,F(xiàn)在主流的編程語(yǔ)言都包含了函數(shù)式編程的特性,Java8在進(jìn)化過(guò)程中吸收了該特性,作為面向編程對(duì)象的補(bǔ)充。

本次的分享主要圍繞以下兩個(gè)方面:

Lambda入門(mén)

多線程技術(shù)

一、Lambda入門(mén)

Lambda起源于數(shù)學(xué)中的λ演算中的一個(gè)匿名函數(shù),從它的起源我們可以知道,Lambda本身就是一個(gè)匿名函數(shù),是Java8才推出的亮點(diǎn),體現(xiàn)了函數(shù)式編程的思想?,F(xiàn)在主流的編程語(yǔ)言都包含了函數(shù)式編程的特性,Java8在進(jìn)化過(guò)程中吸收了該特性,作為面向編程對(duì)象的補(bǔ)充。

Lambda基本語(yǔ)法如下圖所示,Lambda語(yǔ)法較為簡(jiǎn)單,和普通函數(shù)相比,沒(méi)有返回值以及函數(shù)名,它的參數(shù)和執(zhí)行語(yǔ)句之間通過(guò)->連接,表示參數(shù)將傳遞到語(yǔ)句中執(zhí)行。Lambda表達(dá)式還有兩種簡(jiǎn)化表達(dá)式的方法,當(dāng)表達(dá)式中只有一個(gè)執(zhí)行語(yǔ)句時(shí),可以省略語(yǔ)句的{};如果接口的抽象方法只有一個(gè)形參,()可以省略,只需要參數(shù)的名稱(chēng)即可。Lambda可以替代特定匿名內(nèi)部類(lèi),Lambda表達(dá)式不能單獨(dú)存在,在使用時(shí)必須繼承函數(shù)式接口。

下圖示例中的***個(gè)Lambda表達(dá)式,形參列表的數(shù)據(jù)類(lèi)型會(huì)自動(dòng)推斷,只需要參數(shù)名稱(chēng)。

代碼示例:

在上圖展示的代碼中,代碼中的匿名內(nèi)部類(lèi)繼承了Flyable接口,實(shí)現(xiàn)了接口中的fly()方法。代碼準(zhǔn)備了Lambda表達(dá)式重新實(shí)現(xiàn)了Flyable接口。根據(jù)代碼中的輸出命令,執(zhí)行結(jié)果顯示Lambda表達(dá)式起到了和匿名內(nèi)部類(lèi)相同的作用。代碼中,并沒(méi)有定義Lambda表達(dá)式的參數(shù)類(lèi)型,但是我們也可以在Lambda表達(dá)式中定義符合要求的類(lèi)型flyable=(int t)->System.out.println(“I can fly by Lambda”),如果參數(shù)類(lèi)型與接口中方法參數(shù)類(lèi)型不一致flyable=(String t)->System.out.println(“I can fly by Lambda”),編譯器就會(huì)報(bào)錯(cuò)。 

假如接口實(shí)現(xiàn)了兩個(gè)方法,匿名內(nèi)部類(lèi)可以重寫(xiě)新的方法。但是,Lambda表達(dá)式?jīng)]法做到這一點(diǎn),編譯后,將會(huì)提示發(fā)現(xiàn)有多個(gè)需要重寫(xiě)的抽象方法。因此,Lambda表達(dá)式在實(shí)現(xiàn)接口時(shí),只允許接口中有一個(gè)抽象方法,我們將這樣的接口稱(chēng)為函數(shù)式接口,Java8中提供了注解@FunctionalInterface檢驗(yàn)接口是否為函數(shù)式接口,如果不是,注解將會(huì)報(bào)錯(cuò)。另外,代碼嘗試使用Lambda表達(dá)式替代抽象類(lèi)的匿名內(nèi)部類(lèi)的寫(xiě)法,但會(huì)報(bào)錯(cuò),提示必須繼承函數(shù)式接口。因此,Lambda可以替代特定匿名內(nèi)部類(lèi),簡(jiǎn)化代碼,但是必須繼承函數(shù)式接口。

二、多線程技術(shù)

1.進(jìn)程與線程

進(jìn)程是具有一定獨(dú)立功能的程序,關(guān)于某個(gè)數(shù)據(jù)集合上的一次運(yùn)行活動(dòng),是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。線程是進(jìn)程的一個(gè)實(shí)體,是CPU分配調(diào)度的基本單位,代碼的執(zhí)行體。從概念上,我們可以知道進(jìn)程是程序的一次運(yùn)行活動(dòng),需要系統(tǒng)進(jìn)行分配和調(diào)度的;線程是最終代碼的執(zhí)行體,是CPU分配調(diào)度的基本單位。同一個(gè)進(jìn)程中可以包括多個(gè)線程,并且線程共享整個(gè)進(jìn)程的資源,一個(gè)進(jìn)程至少包括一個(gè)線程。如果在理解概念時(shí)很費(fèi)解,想要充分理解這些概念,我們可以采用反抽象的方法,即聯(lián)系,我們需要在實(shí)際生活中尋找符合概念描述的事物。舉例說(shuō)明:我們經(jīng)常說(shuō)安卓手機(jī)比較卡,手機(jī)上App跑的太多,導(dǎo)致內(nèi)存不足,那么我們?cè)谑謾C(jī)上看到的這些App,就是一個(gè)個(gè)程序;在手機(jī)卡頓時(shí),雙擊home鍵,看到有App在后臺(tái)運(yùn)行,這是我們看到的這些app就是進(jìn)程。進(jìn)程是需要系統(tǒng)分配資源的,資源相當(dāng)于手機(jī)的內(nèi)存。通過(guò)這個(gè)例子,我們可以加深對(duì)進(jìn)程和程序概念上的理解。另外,我們也可以通過(guò)反抽象的方法理解進(jìn)程與線程的概念。舉例說(shuō)明:公司運(yùn)轉(zhuǎn)與員工工作,這里的公司,我們可以對(duì)應(yīng)到程序;進(jìn)程是程序的運(yùn)行活動(dòng),這里的進(jìn)程,我們可以理解為公司的正常運(yùn)轉(zhuǎn);同時(shí),公司想要正常運(yùn)轉(zhuǎn),離不開(kāi)員工的工作,員工是公司運(yùn)轉(zhuǎn)不可分割的實(shí)體,只有員工才是真正做事的人,因此我們可以將線程類(lèi)比員工。

2.線程的生命周期

下圖為線程的狀態(tài)圖。所謂的生命周期,指的是線程從出生到死亡過(guò)程中,經(jīng)歷的一系列狀態(tài)。線程通過(guò)創(chuàng)建Thread的一個(gè)實(shí)例new Thread()進(jìn)入new新建狀態(tài);之后調(diào)用start()方法進(jìn)入等待被分配時(shí)間片,進(jìn)入runnable狀態(tài);之后,線程獲得CPU資源執(zhí)行任務(wù),進(jìn)入running狀態(tài);當(dāng)線程執(zhí)行完畢或被其它線程殺死,線程就進(jìn)入dead死亡狀態(tài);如果由于某種原因?qū)е抡谶\(yùn)行的線程讓出CPU并暫停自己的執(zhí)行,即進(jìn)入blocked堵塞狀態(tài),在多種條件下,blocked狀態(tài)可以恢復(fù)成runnable狀態(tài),最終在線程重新拿到時(shí)間片后,就可以進(jìn)入running狀態(tài)重新運(yùn)行。在running狀態(tài)下,如果時(shí)間片用完了或者線程主動(dòng)放棄CPU的使用,線程重新回到runnable狀態(tài)。

時(shí)間片指的是CPU的時(shí)間片段,CPU將它的可執(zhí)行時(shí)間分成很多片段,每個(gè)片段隨機(jī)分配給處在runnable狀態(tài)下的線程,這樣可以達(dá)到并發(fā)的效果。假設(shè)我有一個(gè)單核的CPU,通過(guò)分割很多的時(shí)間片,每個(gè)程序都有機(jī)會(huì)運(yùn)行,仍然可以跑很多的程序,宏觀上看是并發(fā)的,但是由于只有一個(gè)CPU,實(shí)際上程序還是串行的。

我們可以通過(guò)閱讀JDK的Thread類(lèi)注釋?zhuān)瑒?chuàng)建并使用線程,如下圖所示。

按照J(rèn)DK的注釋?zhuān)聢D代碼中使用了兩種創(chuàng)建線程的方法。由于Runnable是一個(gè)函數(shù)式接口,因此代碼中使用Lambda表達(dá)式替代匿名內(nèi)部類(lèi),再將runnable傳遞給Thread,使用start()啟動(dòng)線程。

上述代碼結(jié)果如下圖所示。在下圖代碼中,如果我們將t.start();替換成t.run(),打印結(jié)果將會(huì)變成:

Thread Thread run

Main runnable run.

Main

這說(shuō)明run()方法并沒(méi)有真正啟動(dòng)線程,run()方法只是在當(dāng)前的線程中執(zhí)行了run中的函數(shù)。

3. 線程協(xié)作

并行與協(xié)作:線程在并發(fā)的過(guò)程中更多的是協(xié)作關(guān)系,就像之前的概念中所提到的,進(jìn)程是系統(tǒng)資源分配的單位,線程本身并沒(méi)有多少分配資源,除了維護(hù)自己必須的內(nèi)存開(kāi)銷(xiāo)之外,線程的所有資源都是在進(jìn)程中。多線程在使用競(jìng)爭(zhēng)中資源時(shí),存在搶占或者說(shuō)是共享的關(guān)系。

這時(shí),多線程之間該如何協(xié)作,是需要我們?nèi)ソ鉀Q的。我們通過(guò)下面的代碼,學(xué)會(huì)使用關(guān)鍵字synchronized,以及理解臨界區(qū),鎖的概念。

上圖代碼模擬售票操作。一共有10張票,三個(gè)售票員sellerA,seller,sellerC一起去售票,sell( )方法模擬售票行為。代碼啟動(dòng)線程之后,運(yùn)行結(jié)果如下圖所示。售票員sellerA在一個(gè)時(shí)間片內(nèi)將sell方法中的代碼全部跑完,票售空,但是sellerB與sellerC在線程并發(fā)時(shí),也售出了第10張票,存在重復(fù)售票,這樣的操作是不合理的。

 

為了解決重復(fù)售票的問(wèn)題,我們可以使用Java中提供的同步關(guān)鍵字synchronized修飾sell( )方法,代碼如下圖所示。使用關(guān)鍵字synchronized修飾后,多線程在訪問(wèn)sell( )方法時(shí),能保證只有一個(gè)線程執(zhí)行這個(gè)方法,當(dāng)前線程執(zhí)行完sell( )方法后,其他線程才能執(zhí)行sell( )方法。

執(zhí)行上述代碼后,輸出結(jié)果如下圖所示。從下面結(jié)果可以看到,代碼解決了重復(fù)售票的不合理問(wèn)題,但是仍然只有sellerA一個(gè)在售票。原因在于,通過(guò)關(guān)鍵字synchronized修飾sell( )方法后,sellerA在拿到sell( )方法的執(zhí)行權(quán)時(shí),把里面的代碼一口氣執(zhí)行完了,也就是將票全部賣(mài)出,等sellerA執(zhí)行完后,sellerB和sellerC再執(zhí)行sell( )方法時(shí),票數(shù)已經(jīng)為0,自然會(huì)出現(xiàn)下圖中沒(méi)有賣(mài)出一張票的現(xiàn)象。我們將方法sell( )中的內(nèi)容叫做臨界區(qū),當(dāng)一個(gè)線程進(jìn)入臨界區(qū)后,其他線程必須等待該線程執(zhí)行完臨界區(qū)內(nèi)容后,才能進(jìn)入該臨界區(qū)。

下圖所示的代碼改善了上述sellerA一口氣賣(mài)完所有票的現(xiàn)象。代碼在方法體內(nèi)使用關(guān)鍵字synchronized,括號(hào)中的this表示一個(gè)對(duì)象或者一個(gè)類(lèi)。代碼相較于上面的解決方法,將臨界區(qū)從整個(gè)方法縮小到兩行代碼。也就是說(shuō)多線程在執(zhí)行這兩行代碼時(shí)是同步的。

上圖代碼執(zhí)行結(jié)果如下圖所示。從圖中我們可以發(fā)現(xiàn),不再是只有sellerA在賣(mài)票。并且代碼每次執(zhí)行結(jié)果都是不一樣的,因?yàn)镃PU的時(shí)間片是隨機(jī)給出的。上述代碼中的try catch方法塊使線程睡50ms,延長(zhǎng)售票操作的時(shí)間,在這段時(shí)間內(nèi)可以執(zhí)行其他的操作(比如,將該票給某個(gè)顧客)。代碼改善過(guò)后,保證資源不是被獨(dú)占的,使資源分配均勻。

從上圖我們發(fā)現(xiàn),存在無(wú)效票,原因在于:假設(shè)當(dāng)前票數(shù)為1,A進(jìn)入臨界區(qū)售票,而此時(shí)B已經(jīng)進(jìn)行判斷,在臨界區(qū)外等待了。當(dāng)A賣(mài)完票后,票數(shù)為0,但是B還是會(huì)進(jìn)入臨界區(qū)進(jìn)行售票操作,因此,出現(xiàn)無(wú)效票-1的情況。這說(shuō)明代碼需要進(jìn)一步改善。改善后的代碼如下圖所示。代碼在臨界區(qū)內(nèi)加入判斷條件,只有票數(shù)大于0時(shí),才會(huì)進(jìn)行售票操作,這是常用的雙重檢驗(yàn)方法。經(jīng)過(guò)雙重檢驗(yàn)后,運(yùn)行代碼就不會(huì)出現(xiàn)無(wú)效售票。

下面介紹另外一種單線程同步的方法。代碼如下圖所示。代碼通過(guò)Lock接口定義了一個(gè)鎖,使用ReentrantLock實(shí)現(xiàn)。鎖和上面提到的關(guān)鍵字synchronized作用是一樣的,都是定義出一個(gè)臨界區(qū),讓線程進(jìn)入臨界區(qū)時(shí)實(shí)現(xiàn)線程同步。代碼通過(guò)lock.lock( )定義臨界區(qū)的初始點(diǎn),使用在try語(yǔ)句塊中定義臨界區(qū)執(zhí)行內(nèi)容, finally語(yǔ)句塊中采用unlock( )方法進(jìn)行解鎖。在unlock后線程才算真正走出臨界區(qū)。使用try,finally的原因在于:如果try中拋出異常,如果沒(méi)有finally中的解鎖,線程不會(huì)調(diào)用unlock方法,永遠(yuǎn)占用這把鎖,導(dǎo)致其他線程無(wú)法進(jìn)入臨界區(qū)執(zhí)行代碼。在finally中調(diào)用unlock( )方法保證無(wú)論什么情況下,鎖終將被釋放。避免死鎖。

上圖中的代碼,如果線程遇到售賣(mài)同一張票,鎖沒(méi)有被釋放,線程將會(huì)等待。改善這種情況的方法是,我們使用10把鎖,使得每張票都有一把鎖,當(dāng)線程A售賣(mài)某張票時(shí),其他線程可以跳過(guò)這張票,無(wú)需等待去賣(mài)其他未售出的票。或者,使用兩把鎖,五張票一把鎖,這種分段鎖的策略進(jìn)一步提高了并發(fā)的效率。

4. 線程池

線程雖然不占用進(jìn)程中的資源,但在Java中,如果每當(dāng)一個(gè)請(qǐng)求到達(dá)就創(chuàng)建一個(gè)新線程,開(kāi)銷(xiāo)是相當(dāng)大的。并且,如果在一個(gè)JVM里創(chuàng)建太多的線程,可能會(huì)導(dǎo)致系統(tǒng)由于過(guò)度消耗內(nèi)存導(dǎo)致系統(tǒng)資源不足,為了防止資源不足,應(yīng)該盡可能減少創(chuàng)建和銷(xiāo)毀線程的次數(shù),特別是一些資源耗費(fèi)比較大的線程的創(chuàng)建和銷(xiāo)毀,盡量復(fù)用已有對(duì)象來(lái)進(jìn)行服務(wù),這就線程池技術(shù)產(chǎn)生的原因。如果想要實(shí)現(xiàn)線程的復(fù)用,我們需要繼承線程,在run方法中通過(guò)循環(huán)不斷從外部獲取runnable的實(shí)現(xiàn),以此達(dá)到線程復(fù)用的目的。有了復(fù)用后,可以提供線程池,管理線程,線程池可以控制線程的并發(fā)度,同時(shí),通過(guò)對(duì)多個(gè)任務(wù)重用線程,線程創(chuàng)建的開(kāi)銷(xiāo)就被分?jǐn)偟搅硕鄠€(gè)任務(wù)上了,而且由于在請(qǐng)求到達(dá)時(shí)線程已經(jīng)存在,所以消除了線程創(chuàng)建所帶來(lái)的延遲。 

下面介紹一下線程池的使用。下圖代碼中展示了ThreadPoolExecutor的構(gòu)造方法,下面介紹一下方法中包含的參數(shù)。 

  • corePoolSize:表示線程池的核心線程數(shù),指線程池中常駐線程的數(shù)量,核心線程數(shù)會(huì)一直在線程池中存活,除非線程池停止使用被資源回收了。
  • maximumPoolSize:指線程池所能容納的***線程數(shù)量,當(dāng)活動(dòng)線程數(shù)到達(dá)這個(gè)數(shù)值后,后續(xù)的新任務(wù)將會(huì)被阻塞。
  • keepAliveTime:非核心線程閑置時(shí)的超時(shí)時(shí)長(zhǎng),超過(guò)這個(gè)時(shí)長(zhǎng),非核心線程就會(huì)被回收。當(dāng)ThreadPoolExecutor的allowCoreThreadTimeOut屬性設(shè)置為true時(shí),keepAliveTime同樣會(huì)作用于核心線程。
  • Unit:用于指定keepAliveTime參數(shù)的時(shí)間單位。
  • workQueue:表示線程池中的任務(wù)隊(duì)列(阻塞隊(duì)列),通過(guò)線程池的execute方法提交Runnable對(duì)象會(huì)存儲(chǔ)在這個(gè)隊(duì)列中。
  • threadFactory:表示線程工廠,為線程池提供創(chuàng)建新線程的功能。
  • RejectExecutionHandler:這個(gè)參數(shù)表示當(dāng)ThreadPoolExecutor已經(jīng)關(guān)閉或者已經(jīng)飽和時(shí)(達(dá)到了***線程池大小而且工作隊(duì)列已經(jīng)滿),提供以下幾個(gè)策略考慮是否拒絕到達(dá)的任務(wù)。DiscardPolicy:直接忽略提交的任務(wù)
  • AbortPolicy:忽略提交的任務(wù),在拒絕的同時(shí)拋出異常,通知調(diào)用者拒絕執(zhí)行
  • CallerRunsPolicy:讓線程池的使用者所在的線程運(yùn)行提交的任務(wù)調(diào)用者
  • DiscardOlderestPolicy:忽略最早放到隊(duì)列中的任務(wù)

下圖代碼中自定義了一個(gè)線程池。通過(guò)線程池的submit( )方法提交runnable的實(shí)現(xiàn),最終通過(guò)線程池的shutdown( )方法關(guān)閉線程池。

Java包中預(yù)置的線程池有以下幾種:newSingleThreadExecutor;newFixedThreadPool:newCachedThreadPool: newScheduledThreadPool: 但在阿里巴巴的Java開(kāi)發(fā)中是不建議甚至禁止使用Java預(yù)置線程池的。下圖中的代碼目的是尋找SingleThreadExecutor的bug。

 

上述代碼的運(yùn)行結(jié)果如下圖所示。代碼利用循環(huán),***添加runnable的實(shí)現(xiàn),但是由于單一線程的阻塞隊(duì)列是沒(méi)有邊界的,會(huì)導(dǎo)致添加的對(duì)象過(guò)多,耗盡內(nèi)存資源。因此阿里巴巴開(kāi)發(fā)手冊(cè)是明確禁止使用Java預(yù)置線程池的。

 

如果對(duì)JAVA微服務(wù)、分布式、高并發(fā)、高可用、大型互聯(lián)網(wǎng)架構(gòu)技術(shù)、面試經(jīng)驗(yàn)交流。感興趣可以關(guān)注我的頭條號(hào),我會(huì)在微頭條不定期的發(fā)放免費(fèi)的資料鏈接,這些資料都是從各個(gè)技術(shù)網(wǎng)站搜集、整理出來(lái)的,如果你有好的學(xué)習(xí)資料可以私聊發(fā)我,我會(huì)注明出處之后分享給大家。歡迎分享,歡迎評(píng)論,歡迎轉(zhuǎn)發(fā)! 

責(zé)任編輯:龐桂玉 來(lái)源: 今日頭條
相關(guān)推薦

2022-06-07 07:37:40

線程進(jìn)程開(kāi)發(fā)

2012-05-14 13:49:56

2014-02-10 10:13:43

2019-07-31 09:06:35

Java跳槽那些事兒文章

2011-08-03 09:20:30

Python

2019-10-31 08:36:59

線程內(nèi)存操作系統(tǒng)

2016-09-27 13:47:15

Linux網(wǎng)絡(luò)命令

2019-05-22 11:40:12

物聯(lián)網(wǎng)無(wú)線技術(shù)IOT

2022-08-01 08:37:45

Java池化緩存

2023-02-24 14:46:32

Java線程池編程

2018-09-21 11:11:34

備份離線自動(dòng)

2018-11-08 12:07:38

備份手動(dòng)磁盤(pán)

2024-04-15 00:02:00

Java補(bǔ)丁技術(shù)

2014-02-18 17:09:56

網(wǎng)絡(luò)·安全技術(shù)周刊

2023-01-28 09:50:17

java多線程代碼

2020-12-10 11:00:37

JavaJVM命令

2020-02-25 17:13:15

移動(dòng)開(kāi)發(fā)iOSAndroid

2011-05-20 13:52:31

2011-06-21 10:02:29

Python

2023-04-26 16:34:12

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)