類加載機(jī)制
虛擬機(jī)把描述類的數(shù)據(jù)從 Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的java類型,這就是虛擬機(jī)的類加載機(jī)制。
類的生命周期
加載(Loading)
驗(yàn)證(Verification)
準(zhǔn)備(Preparation)
解析(Resolution)
初始化(Initialization)
使用(Using)
卸載(Unloading)
類加載的過(guò)程
類的加載過(guò)程包括了加載,驗(yàn)證,準(zhǔn)備,解析,初始化
類的加載主要分為以下三步:
1. 加載:根據(jù)路徑找到對(duì)應(yīng)的.class文件
這一步會(huì)使用到類加載器。
加載是類加載的一個(gè)階段,注意不要混淆。
加載過(guò)程完成以下三件事:
通過(guò)類的完全限定名稱獲取定義該類的二進(jìn)制字節(jié)流。
將該字節(jié)流表示的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)存儲(chǔ)結(jié)構(gòu)。
在內(nèi)存中生成一個(gè)代表該類的 Class對(duì)象,作為方法區(qū)中該類各種數(shù)據(jù)的訪問(wèn)入口。
2. 連接:
驗(yàn)證:檢查待加載的class正確性;
準(zhǔn)備:給類的靜態(tài)變量分配空間,此時(shí)靜態(tài)變量還是零值(還沒(méi)到初始化的階段)
解析:將常量池的符號(hào)引用轉(zhuǎn)為直接引用
符號(hào)引用:
符號(hào)引用是用一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)可以無(wú)歧義的定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定已經(jīng)加載到內(nèi)存中。
直接引用:
直接引用可以是直接指向目標(biāo)的指針,相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的,同一符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一半不會(huì)相同,如果有了直接引用,那引用目標(biāo)必定已經(jīng)在內(nèi)存中存在。
注意:實(shí)例變量不會(huì)在這階段分配內(nèi)存,它會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起被分配在堆中。應(yīng)該注意到,實(shí)例化不是類加載的一個(gè)過(guò)程,類加載發(fā)生在所有實(shí)例化操作之前,并且類加載只進(jìn)行一次,實(shí)例化可以進(jìn)行多次。
3. 初始化:對(duì)靜態(tài)變量和靜態(tài)代碼塊執(zhí)行初始化工作
初始化階段才真正開(kāi)始執(zhí)行類中定義的 Java 程序代碼。初始化階段是虛擬機(jī)執(zhí)行類構(gòu)造器 () 方法的過(guò)程。在準(zhǔn)備階段,類變量已經(jīng)賦過(guò)一次系統(tǒng)要求的初始值,而在初始化階段,根據(jù)程序員通過(guò)程序制定的主觀計(jì)劃去初始化類變量和其它資源。
總結(jié)
在Java中,類裝載器把一個(gè)類裝入Java虛擬機(jī)中,要經(jīng)過(guò)三個(gè)步驟來(lái)完成:加載、連接和初始化,其中鏈接又可以分成校驗(yàn)、準(zhǔn)備和解析三 步,除了解析外,其它步驟是嚴(yán)格按照順序完成的,各個(gè)步驟的主要工作如下:
裝載:查找和導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù);
鏈接:執(zhí)行下面的校驗(yàn)、準(zhǔn)備和解析步驟,其中解析步驟是可以選擇的;
校驗(yàn):檢查導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù)的正確性;
準(zhǔn)備:給類的靜態(tài)變量分配并初始化存儲(chǔ)空間;
解析:將符號(hào)引用轉(zhuǎn)成直接引用
初始化:激活類的靜態(tài)變量的初始化Java代碼和靜態(tài)Java代碼塊
類初始化的時(shí)機(jī)
創(chuàng)建類的實(shí)例。new,反射,反序列化
使用某類的類方法–靜態(tài)方法
訪問(wèn)某類的類變量,或賦值類變量
反射創(chuàng)建某類或接口的Class對(duì)象。Class.forName(“Hello”);—注意:loadClass調(diào)用ClassLoader.loadClass(name,false)方法,沒(méi)有l(wèi)ink,自然沒(méi)有initialize
初始化某類的子類
直接使用java.exe來(lái)運(yùn)行某個(gè)主類。即cmd java 程序會(huì)先初始化該類。
類的加載器(ClassLoader)
類加載器雖然只用于實(shí)現(xiàn)類的加載動(dòng)作,但是還起到判別兩個(gè)類是否相同的作用。
對(duì)于任何一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立其在java虛擬機(jī)中的唯一性。
一個(gè)java程序由若干個(gè).class文件組成,當(dāng)程序在運(yùn)行時(shí),會(huì)調(diào)用該程序的一個(gè)入口函數(shù)來(lái)調(diào)用系統(tǒng)的相關(guān)功能,而這些功能都被封裝在不同的class文件中。
程序在啟動(dòng)時(shí),并不會(huì)一次性加載程序所要用的所有class文件,而是根據(jù)程序的需要,通過(guò)java的類加載機(jī)制來(lái)動(dòng)態(tài)加載某個(gè).class文件到內(nèi)存當(dāng)中,從而只有class文件被載入到了內(nèi)存之后,才能被其他class引用,所以類的加載器就是用來(lái)動(dòng)態(tài)加載class文件到內(nèi)存當(dāng)中用的。
類加載器如何判斷是同樣的類
java中一個(gè)類用 全限定類名標(biāo)識(shí)——包名+類名
jvm中一個(gè)類用其 全限定類名+加載器標(biāo)識(shí)——包名+類名+加載器名
類加載器的種類
從虛擬機(jī)的角度來(lái)分:
一種是啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類加載器使用C++語(yǔ)言實(shí)現(xiàn)(HotSpot虛擬機(jī)中),是虛擬機(jī)自身的一部分;
另一種就是所有其他的類加載器,這些類加載器都有Java語(yǔ)言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全部繼承自java.lang.ClassLoader。
從開(kāi)發(fā)者角度來(lái)分:
啟動(dòng)(Bootstrap)類加載器:負(fù)責(zé)將Java_Home/lib下面的類庫(kù)加載到內(nèi)存中(比如rt.jar)。由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),開(kāi)發(fā)者無(wú)法直接獲取到啟動(dòng)類加載器的引用,所以==不允許直接通過(guò)引用進(jìn)行操作。==加載java核心類
擴(kuò)展(Extension)類加載器:它負(fù)責(zé)將Java_Home /lib/ext或者由系統(tǒng)變量 java.ext.dir指定位置中的類庫(kù)加載到內(nèi)存中。開(kāi)發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。
應(yīng)用程序(Application)類加載器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,因此一般稱為系統(tǒng)類加載器。它負(fù)責(zé)加載用戶類路徑(CLASSPATH)中指定的類庫(kù)。開(kāi)發(fā)者可以直接使用系統(tǒng)類加載器。默認(rèn)使用
雙親機(jī)制
這里的類加載器不是以繼承的關(guān)系來(lái)實(shí)現(xiàn),都是以組合關(guān)系復(fù)用父類加載器的代碼。
定義:
某個(gè)特定的類加載器在接到加載類的請(qǐng)求時(shí),首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無(wú)法完成此加載任務(wù)時(shí),才自己去加載。
使用雙親委派機(jī)制好處在于java類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。
具體的,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,如果沒(méi)加載到,則把任務(wù)轉(zhuǎn)交給Extension ClassLoader試圖加載,如果也沒(méi)加載到,則轉(zhuǎn)交給App ClassLoader 進(jìn)行加載,如果它也沒(méi)有加載得到的話,則返回給委托的發(fā)起者,由它到指定的文件系統(tǒng)或網(wǎng)絡(luò)等URL中加載該類。
如果它們都沒(méi)有加載到這個(gè)類時(shí),則拋出ClassNotFoundException異常。否則將這個(gè)找到的類生成一個(gè)類的定義,并將它加載到內(nèi)存當(dāng)中,最后返回這個(gè)類在內(nèi)存中的Class實(shí)例對(duì)象。
為什么要使用雙親委托這種模型?
雙親委托機(jī)制可以避免重復(fù)加載,當(dāng)父親已經(jīng)加載了該類的時(shí)候,就沒(méi)有必要子ClassLoader再加載一次。
考慮到安全因素,我們?cè)囅胍幌?,如果不使用這種委托模式,那我們就可以隨時(shí)使用自定義的String來(lái)動(dòng)態(tài)替代java核心api中定義的類型,這樣會(huì)存在非常大的安全隱患,而雙親委托的方式,就可以避免這種情況,因?yàn)镾tring已經(jīng)在啟動(dòng)時(shí)就被引導(dǎo)類加載器(Bootstrcp ClassLoader)加載,所以用戶自定義的ClassLoader永遠(yuǎn)也無(wú)法加載一個(gè)自己寫的String,除非你改變JDK中ClassLoader搜索類的默認(rèn)算法。
JVM在搜索類的時(shí)候,又是如何判定兩個(gè)class是相同的呢
JVM在判定兩個(gè)class是否相同時(shí),不僅要判斷兩個(gè)類名是否相同,而且要判斷是否由同一個(gè)類加載器實(shí)例加載的。只有兩者同時(shí)滿足的情況下,JVM才認(rèn)為這兩個(gè)class是相同的。就算兩個(gè)class是同一份class字節(jié)碼,如果被兩個(gè)不同的ClassLoader實(shí)例所加載,JVM也會(huì)認(rèn)為它們是兩個(gè)不同class。
JAVA內(nèi)存模型JMM
Java虛擬機(jī)規(guī)范試圖定義一種Java內(nèi)存模型(JMM),來(lái)屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問(wèn)差異,讓Java程序在各種平臺(tái)上都能達(dá)到一致的內(nèi)存訪問(wèn)效果。內(nèi)存模型的作用就是控制一個(gè)線程的變量,什么時(shí)候?qū)ζ渌€程可見(jiàn)。
Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問(wèn)規(guī)則,即在JVM中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)。此處的變量與Java編程里面的變量有所不同步,它包含了實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但不包含局部變量和方法參數(shù),因?yàn)楹笳呤蔷€程私有的,不會(huì)共享,當(dāng)然不存在數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。
JMM規(guī)定了所有的變量都存儲(chǔ)在主內(nèi)存(MainMemory)中。每個(gè)線程還有自己的工作內(nèi)存(WorkingMemory),線程的工作內(nèi)存中保存了該線程使用到的變量的主內(nèi)存的副本拷貝,線程對(duì)變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行,而不能直接讀寫主內(nèi)存中的變量(volatile變量仍然有工作內(nèi)存的拷貝,但是由于它特殊的操作順序性規(guī)定,所以看起來(lái)如同直接在主內(nèi)存中讀寫訪問(wèn)一般)。不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程之間值的傳遞都需要通過(guò)主內(nèi)存來(lái)完成。
Java線程之間的通信由Java內(nèi)存模型(本文簡(jiǎn)稱為JMM)控制,JMM決定一個(gè)線程對(duì)共享變量的寫入何時(shí)對(duì)另一個(gè)線程可見(jiàn)。
從抽象的角度來(lái)看,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(main memory)中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(local memory),本地內(nèi)存中存儲(chǔ)了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個(gè)抽象概念,并不真實(shí)存在。它涵蓋了緩存,寫緩沖區(qū),寄存器以及其他的硬件和編譯器優(yōu)化。
內(nèi)存間交互操作
java內(nèi)存模型中定義了以下8種操作來(lái)完成,虛擬機(jī)實(shí)現(xiàn)時(shí)必須保證下面提及的每一種操作都是原子的、不可再分的。
lock鎖定:作用于主內(nèi)存的變量。它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)。
unlock解鎖:作用于主內(nèi)存的變量
read讀?。鹤饔糜谥鲀?nèi)存的變量,把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動(dòng)作使用。
load載入:作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作中內(nèi)存的變量副本中。
use使用:作用于工作內(nèi)存的變量,它把工作內(nèi)存中的一個(gè)變量的值傳遞給執(zhí)行引擎。每當(dāng)虛擬機(jī)遇到一個(gè)需要使用該變量的值的字節(jié)碼指令時(shí)會(huì)執(zhí)行這個(gè)操作。
assign賦值:作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作。
store存儲(chǔ):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用。
write:作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存的變量中。
volatile原理
變量對(duì)線程的可見(jiàn)性,比synchronized性能好
一旦一個(gè)共享變量(類的成員變量、類的靜態(tài)成員變量)被volatile修飾之后,那么就具備了兩層語(yǔ)義:
1)保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見(jiàn)性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來(lái)說(shuō)是立即可見(jiàn)的。
不能認(rèn)為,使用了volatile關(guān)鍵字,就認(rèn)為并發(fā)安全。在一些運(yùn)算中,由于運(yùn)算并非原子操作,還是會(huì)出現(xiàn)同步的問(wèn)題。
2)禁止進(jìn)行指令重排序。
普通變量?jī)H僅會(huì)保證在該方法的執(zhí)行過(guò)程中所有依賴賦值結(jié)果的地方都能獲取到正確的結(jié)果,而不能保證變量賦值操作的順序與程序代碼中的執(zhí)行順序一致。
volatile修飾后,會(huì)加入內(nèi)存屏障(指重排序時(shí)不能把后面的指令重排序到內(nèi)存屏障之前的位置)。執(zhí)行“l(fā)ock addl $0x0,(%esp)”,這個(gè)操作是一個(gè)空操作,作用是使得本cpude Cache寫入了內(nèi)存,該寫入動(dòng)作也會(huì)引起別的cpu或別的內(nèi)核無(wú)效化其cache,這種操作相當(dāng)于對(duì)cache中的變量store 和write操作,使得對(duì)volatile變量的修改對(duì)其他cpu立即可見(jiàn)。
內(nèi)部原理
處理器為了提高處理速度,不直接和內(nèi)存進(jìn)行通訊,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存(L1,L2或其他)后再進(jìn)行操作,但操作完之后不知道何時(shí)會(huì)寫到內(nèi)存,如果對(duì)聲明了Volatile變量進(jìn)行寫操作,JVM就會(huì)向處理器發(fā)送一條Lock前綴的指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。
但是就算寫回到內(nèi)存,如果其他處理器緩存的值還是舊的,再執(zhí)行計(jì)算操作就會(huì)有問(wèn)題,所以在多處理器下,為了保證各個(gè)處理器的緩存是一致的,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議,每個(gè)處理器通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查自己緩存的值是不是過(guò)期了,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器要對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候,會(huì)強(qiáng)制重新從系統(tǒng)內(nèi)存里把數(shù)據(jù)讀到處理器緩存里。
volatile關(guān)鍵字
被volatile修飾的共享變量,就具有了以下兩點(diǎn)特性:
保證了不同線程對(duì)該變量操作的內(nèi)存可見(jiàn)性;
禁止指令重排序;
JMM三大特性
原子性
原子性即一個(gè)操作或一系列是不可中斷的。即使是在多個(gè)線程的情況下,操作一旦開(kāi)始,就不會(huì)被其他線程干擾。
比如,對(duì)于一個(gè)靜態(tài)變量int x兩條線程同時(shí)對(duì)其賦值,線程A賦值為1,而線程B賦值為2,不管線程如何運(yùn)行,最終x的值要么是1,要么是2,線程A和線程B間的操作是沒(méi)有干擾的,這就是原子性操作,不可被中斷的。
由jmm來(lái)直接保證的原子性變量操作包括read,load,assign,use,store,write,我們大致可以認(rèn)為基本數(shù)據(jù)類型的訪問(wèn)讀寫是具備原子性的,(double和long例外)。此外,synchronized塊之間的代碼也具有原子性
可見(jiàn)性
可見(jiàn)性指的是,當(dāng)一個(gè)線程修改了共享變量的值后,其他線程能夠立即得知這個(gè)修改。volatile變量、synchronized,final三個(gè)關(guān)鍵字修飾的變量都可保證原子性。
有序性
在Java內(nèi)存模型中有序性可歸納為這樣一句話:如果在本線程內(nèi)觀察,所有操作都是有序的,如果在一個(gè)線程中觀察另一個(gè)線程,所有操作都是無(wú)序的。
有序性是指對(duì)于單線程的執(zhí)行代碼,執(zhí)行是按順序依次進(jìn)行的。但在多線程環(huán)境中,則可能出現(xiàn)亂序現(xiàn)象,因?yàn)樵诰幾g過(guò)程會(huì)出現(xiàn)“指令重排”,重排后的指令與原指令的順序未必一致。因此,上面歸納的前半句指的是線程內(nèi)保證串行語(yǔ)義執(zhí)行,后半句則指“指令重排”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。
java語(yǔ)言提供了volatile和synchronized兩個(gè)關(guān)鍵字來(lái)保證線程之間操作的有序性,volatile關(guān)鍵字本身就包含了禁止指令重排的語(yǔ)義,而synchronized則是由一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作這條規(guī)則獲得的。
指令重排
CPU和編譯器為了提升程序執(zhí)行的效率,會(huì)按照一定的規(guī)則允許進(jìn)行指令優(yōu)化。但代碼邏輯之間是存在一定的先后順序,并發(fā)執(zhí)行時(shí)按照不同的執(zhí)行邏輯會(huì)得到不同的結(jié)果。
volatile關(guān)鍵詞修飾的變量,會(huì)禁止指令重排的操作,從而在一定程度上避免了多線程中的問(wèn)題
volatile不能保證原子性,它只是對(duì)單個(gè)volatile變量的讀/寫具有原子性,但是對(duì)于類似i++這樣的復(fù)合操作就無(wú)法保證了。
剛提到synchronized,能說(shuō)說(shuō)它們之間的區(qū)別嗎
volatile本質(zhì)是在告訴JVM當(dāng)前變量在寄存器(工作內(nèi)存)中的值是不確定的,需要從主存中讀??;synchronized則是鎖定當(dāng)前變量,只有當(dāng)前線程可以訪問(wèn)該變量,其他線程被阻塞住。
volatile僅能使用在變量級(jí)別;synchronized則可以使用在變量、方法和類級(jí)別的;
volatile僅能實(shí)現(xiàn)變量的修改可見(jiàn)性,不能保證原子性;而synchronized則可以保證變量的修改可見(jiàn)性和原子性;
volatile不會(huì)造成線程的阻塞;synchronized可能會(huì)造成線程的阻塞。
volatile標(biāo)記的變量不會(huì)被編譯器優(yōu)化;synchronized標(biāo)記的變量可以被編譯器優(yōu)化。
ABA問(wèn)題
比如說(shuō)線程one從內(nèi)存位置V中取出A,這時(shí)候另一個(gè)線程two也從內(nèi)存中取出A,并且two進(jìn)行了一些操作變成了B,然后two又將V位置的數(shù)據(jù)變成A,這時(shí)候線程one進(jìn)行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后one操作成功。盡管線程one的CAS操作成功,但是不代表這個(gè)過(guò)程就是沒(méi)有問(wèn)題的。如果鏈表的頭在變化了兩次后恢復(fù)了原值,但是不代表鏈表就沒(méi)有變化
要解決"ABA問(wèn)題",我們需要增加一個(gè)版本號(hào),在更新變量值的時(shí)候不應(yīng)該只更新一個(gè)變量值,而應(yīng)該更新兩個(gè)值,分別是變量值和版本號(hào)
原子變量
原子變量不使用鎖或其他同步機(jī)制來(lái)保護(hù)對(duì)其值的并發(fā)訪問(wèn)。所有操作都是基于CAS原子操作的。他保證了多線程在同一時(shí)間操作一個(gè)原子變量而不會(huì)產(chǎn)生數(shù)據(jù)不一致的錯(cuò)誤,并且他的性能優(yōu)于使用同步機(jī)制保護(hù)的普通變量,譬如說(shuō)在多線程環(huán)境 中統(tǒng)計(jì)次數(shù)就可以使用原子變量。
多線程的使用場(chǎng)景
有時(shí)候使用多線程并不是為了提高效率,而是使得CPU能夠同時(shí)處理多個(gè)事件。
為了不阻塞主線程,啟動(dòng)其他線程來(lái)做好事的事情,比如APP中耗時(shí)操作都不在UI中做.
實(shí)現(xiàn)更快的應(yīng)用程序,即主線程專門監(jiān)聽(tīng)用戶請(qǐng)求,子線程用來(lái)處理用戶請(qǐng)求,以獲得大的吞吐量.感覺(jué)這種情況下,多線程的效率未必高。 這種情況下的多線程是為了不必等待,可以并行處理多條數(shù)據(jù)。比如JavaWeb的就是主線程專門監(jiān)聽(tīng)用戶的HTTP請(qǐng)求,然后啟動(dòng)子線程去處理用戶的HTTP請(qǐng)求。
某種雖然優(yōu)先級(jí)很低的服務(wù),但是卻要不定時(shí)去做。
比如Jvm的垃圾回收。
某種任務(wù),雖然耗時(shí),但是不耗CPU的操作時(shí),開(kāi)啟多個(gè)線程,效率會(huì)有顯著提高。
比如讀取文件,然后處理。磁盤IO是個(gè)很耗費(fèi)時(shí)間,但是不耗CPU計(jì)算的工作。 所以可以一個(gè)線程讀取數(shù)據(jù),一個(gè)線程處理數(shù)據(jù)??隙ū纫粋€(gè)線程讀取數(shù)據(jù),然后處理效率高。因?yàn)閮蓚€(gè)線程的時(shí)候充分利用了CPU等待磁盤IO的空閑時(shí)間。
最后
歡迎大家關(guān)注我的公眾號(hào):前程有光,金三銀四跳槽面試季,整理了1000多道將近500多頁(yè)pdf文檔的Java面試題資料,文章都會(huì)在里面更新,整理的資料也會(huì)放在里面。