您好,登錄后才能下訂單哦!
線程就是進(jìn)程中運(yùn)行的多個(gè)子任務(wù),是操作系統(tǒng)調(diào)用的最小單元,線程是獨(dú)立調(diào)度和分派的基本單位。線程可以為操作系統(tǒng)內(nèi)核調(diào)度的內(nèi)核線程,如Win32線程;由用戶進(jìn)程自行調(diào)度的用戶線程。
New: 新建狀態(tài),new 出來,還沒有調(diào)用 start
Runnable: 可運(yùn)行狀態(tài),調(diào)用 start 進(jìn)入可運(yùn)行狀態(tài),可能運(yùn)行也可能沒有運(yùn)行,取決于操作系統(tǒng)的調(diào)度
Blocked: 阻塞狀態(tài),被鎖阻塞,暫時(shí)不活動(dòng),阻塞狀態(tài)是線程阻塞在進(jìn)入synchronized 關(guān)鍵字修飾的方法或代碼塊(獲取鎖)時(shí)的狀態(tài)。
Waiting: 等待狀態(tài),不活動(dòng),不運(yùn)行任何代碼,等待線程調(diào)度器調(diào)度,wait sleep
Timed Waiting: 超時(shí)等待,在指定時(shí)間自行返回
Terminated: 終止?fàn)顟B(tài),包括正常終止和異常終止
a.繼承 Thread 重寫 run 方法
b.實(shí)現(xiàn) Runnable 重寫 run 方法
c.實(shí)現(xiàn) Callable 重寫 call 方法
實(shí)現(xiàn) Callable 和實(shí)現(xiàn) Runnable 類似,但是功能更強(qiáng)大,具體表現(xiàn)在
a.可以在任務(wù)結(jié)束后提供一個(gè)返回值,Runnable 不行
b.call 方法可以拋出異常,Runnable 的 run 方法不行
c.可以通過運(yùn)行 Callable 得到的 Fulture 對(duì)象監(jiān)聽目標(biāo)線程調(diào)用 call 方法的結(jié)果,得到返回值,(fulture.get(),調(diào)用后會(huì)阻塞,直到獲取到返回值)
一般情況下,線程不執(zhí)行完任務(wù)不會(huì)退出,但是在有些場(chǎng)景下,我們需要手動(dòng)控制線程中斷結(jié)束任務(wù),Java 中有提供線程中斷機(jī)制相關(guān)的 Api,每個(gè)線程都一個(gè)狀態(tài)位用于標(biāo)識(shí)當(dāng)前線程對(duì)象是否是中斷狀態(tài)
public boolean isInterrupted() //
判斷中斷標(biāo)識(shí)位是否是 true,不會(huì)改變標(biāo)
識(shí)位 public void interrupt() //
將中斷標(biāo)識(shí)位設(shè)置為 truepublic static<br/>boolean interrupted() //
判斷當(dāng)前線程是否被中斷,并且該方法調(diào)用結(jié)束的時(shí)候會(huì)清空中斷標(biāo)識(shí)位需要注意的是 interrupt()
方法并不會(huì)真的中斷線程,它只是將中斷標(biāo)識(shí)位設(shè)置為 true,具體是否要中斷由程序來判斷,如下,只要線程中斷標(biāo)識(shí)位為 false,也就是沒有中斷就一直執(zhí)行線程方法
new Thread( new Runnable(){
while (!Thread.currentThread().isInterrupted()){
//執(zhí)行線程方法
}
}).start();
前邊我們提到了線程的六種狀態(tài),New Runnable Blocked Waiting Timed WaitingTerminated
,那么在這六種狀態(tài)下調(diào)用線程中斷的代碼會(huì)怎樣呢,New 和Terminated 狀態(tài)下,線程不會(huì)理會(huì)線程中斷的請(qǐng)求,既不會(huì)設(shè)置標(biāo)記位,在Runnable
和 Blocked 狀態(tài)下調(diào)用 interrupt 會(huì)將標(biāo)志位設(shè)置位 true,在 Waiting 和Timed Waiting 狀態(tài)下會(huì)發(fā)生 InterruptedException
異常,針對(duì)這個(gè)異常我們?nèi)绾翁幚恚?/p>
1.在 catch 語句中通過 interrupt 設(shè)置中斷狀態(tài),因?yàn)榘l(fā)生中斷異常時(shí),中斷標(biāo)志位會(huì)被復(fù)位,我們需要重新將中斷標(biāo)志位設(shè)置為 true,這樣外界可以通過這個(gè)狀態(tài)判斷是否需要中斷線程
try {
....
} catch (InterruptedException e){
Thread.currentThread().interrupt();
}
2.更好的做法是,不捕獲異常,直接拋出給調(diào)用者處理,這樣更靈活
從 SUN 的官方文檔可以得知,調(diào)用 Thread.stop()
方法是不安全的,這是因?yàn)楫?dāng)調(diào)用 Thread.stop()
方法時(shí),會(huì)發(fā)生下面兩件事:
- 即刻拋出
ThreadDeath
異常,在線程的run()
方法內(nèi),任何一點(diǎn)都有可能拋出ThreadDeath Error
,包括在 catch 或 finally 語句中。- 釋放該線程所持有的所有的鎖。調(diào)用
thread.stop()
后導(dǎo)致了該線程所持有的所有鎖的突然釋放,那么被保護(hù)數(shù)據(jù)就有可能呈現(xiàn)不一致性,其他線程在使用這些被破壞的數(shù)據(jù)時(shí),有可能導(dǎo)致一些很奇怪的應(yīng)用程序錯(cuò)誤。
1.發(fā)送方進(jìn)程通過系統(tǒng)調(diào)用(copy_from_user)將要發(fā)送的數(shù)據(jù)存拷貝到內(nèi)核緩存區(qū)中。
2.接收方開辟一段內(nèi)存空間,內(nèi)核通過系統(tǒng)調(diào)用(copy_to_user)將內(nèi)核緩存區(qū)中的數(shù)據(jù)拷貝到接收方的內(nèi)存緩存區(qū)。
volatile 為實(shí)例域的同步訪問提供了免鎖機(jī)制,如果聲明一個(gè)域?yàn)関olatile,那么編譯器和虛擬機(jī)就直到該域可能被另一個(gè)線程并發(fā)更新
堆內(nèi)存是被所有線程共享的運(yùn)行時(shí)內(nèi)存區(qū)域,存在可見性的問題。線程之間共享變量存儲(chǔ)在主存中,每個(gè)線程都有一個(gè)私有的本地內(nèi)存,本地內(nèi)存存儲(chǔ)了該線程共享變量的副本(本地內(nèi)存是一個(gè)抽象概念,并不真實(shí)存在),兩個(gè)線程要通信的話,首先 A 線程把本地內(nèi)存更新過的共享變量更新到主存中,然后 B 線程去主存中讀取 A 線程更新過的共享變量,也就是說假設(shè)線程 A 執(zhí)行了 i = 1 這行代碼更新主線程變量 i 的值,會(huì)首先在自己的工作線程中堆變量 i 進(jìn)行賦值,然后再寫入主存當(dāng)中,而不是直接寫入主存
原子性: 對(duì)基本數(shù)據(jù)類型的讀取和賦值操作是原子性操作,這些操作不可被中斷,是一步到位的,例如 x=3 是原子性操作,而 y = x 就不是,
它包含兩步: 第一讀取 x,第二將 x 寫入工作內(nèi)存;x++也不是原子性操作,它包含三部,第一,讀取x,第二,對(duì) x 加 1,第三,寫入內(nèi)存。
原子性操作的類如: AtomicInteger
AtomicBoolean
AtomicLong
AtomicReference
可見性: 指線程之間的可見性,既一個(gè)線程修改的狀態(tài)對(duì)另一個(gè)線程是可見的。volatile 修飾可以保證可見性,它會(huì)保證修改的值會(huì)立即被更新到主存,所以對(duì)其他線程是可見的,普通的共享變量不能保證可見性,因?yàn)楸恍薷暮蟛粫?huì)立即寫入主存,何時(shí)被寫入主存是不確定的,所以其他線程去讀取的時(shí)候可能讀到的還是舊值
有序性: Java 中的指令重排序(包括編譯器重排序和運(yùn)行期重排序)可以起到優(yōu)化代碼的作用,但是在多線程中會(huì)影響到并發(fā)執(zhí)行的正確性,使用 volatile 可以保證有序性,禁止指令重排
volatile 可以保證可見性 有序性,但是無法保證原子性,在某些情況下可以提供優(yōu)于鎖的性能和伸縮性,替代 sychronized
關(guān)鍵字簡(jiǎn)化代碼,但是要嚴(yán)格遵循使用條件。
線程池的工作原理: 線程池可以減少創(chuàng)建和銷毀線程的次數(shù),從而減少系統(tǒng)資源的消耗,當(dāng)一個(gè)任務(wù)提交到線程池時(shí)
a. 首先判斷核心線程池中的線程是否已經(jīng)滿了,如果沒滿,則創(chuàng)建一個(gè)核心線程執(zhí)行任務(wù),否則進(jìn)入下一步
b. 判斷工作隊(duì)列是否已滿,沒有滿則加入工作隊(duì)列,否則執(zhí)行下一步
c. 判斷線程數(shù)是否達(dá)到了最大值,如果不是,則創(chuàng)建非核心線程執(zhí)行任務(wù),否則執(zhí)行飽和策略,默認(rèn)拋出異常
1.FixedThreadPool
: 可重用固定線程數(shù)的線程池,只有核心線程,沒有非核心線程,核心線程不會(huì)被回收,有任務(wù)時(shí),有空閑的核心線程就用核心線程執(zhí)行,沒有則加入隊(duì)列排隊(duì)
2.SingleThreadExecutor
: 單線程線程池,只有一個(gè)核心線程,沒有非核心線程,當(dāng)任務(wù)到達(dá)時(shí),如果沒有運(yùn)行線程,則創(chuàng)建一個(gè)線程執(zhí)行,如果正在運(yùn)行則加入隊(duì)列等待,可以保證所有任務(wù)在一個(gè)線程中按照順序執(zhí)行,和 FixedThreadPool 的區(qū)別只有數(shù)量
3.CachedThreadPool
: 按需創(chuàng)建的線程池,沒有核心線程,非核心線程有Integer.MAX_VALUE 個(gè),每次提交任務(wù)如果有空閑線程則由空閑線程執(zhí)行,沒有空閑線程則創(chuàng)建新的線程執(zhí)行,適用于大量的需要立即處理的并且耗時(shí)較短的任務(wù)
4.ScheduledThreadPoolExecutor
: 繼承自 ThreadPoolExecutor
,用于延時(shí)執(zhí)行任務(wù)或定期執(zhí)行任務(wù),核心線程數(shù)固定,線程總數(shù)為Integer.MAX_VALUE
為什么需要線程同步?當(dāng)多個(gè)線程操作同一個(gè)變量的時(shí)候,存在這個(gè)變量何時(shí)對(duì)另一個(gè)線程可見的問題,也就是可見性。每一個(gè)線程都持有主存中變量的一個(gè)副本,當(dāng)他更新這個(gè)變量時(shí),首先更新的是自己線程中副本的變量值,然后會(huì)將這個(gè)值更新到主存中,但是是否立即更新以及更新到主存的時(shí)機(jī)是不確定的這就導(dǎo)致當(dāng)另一個(gè)線程操作這個(gè)變量的時(shí)候,他從主存中讀取的這個(gè)變量還是舊的值,導(dǎo)致兩個(gè)線程不同步的問題。
線程同步就是為了保證多線程操作的可見性和原子性,比如我們用synchronized 關(guān)鍵字包裹一端代碼,我們希望這段碼執(zhí)行完成后,對(duì)另一個(gè)線程立即可見,另一個(gè)線程再次操作的時(shí)候得到的是上一個(gè)線程更新之后的內(nèi)容,還有就是保證這段代碼的原子性,這段代碼可能涉及到了好幾部操作,我們希望這好幾步的操作一次完成不會(huì)被中間打斷,鎖的同步機(jī)制就可以實(shí)現(xiàn)這一點(diǎn)。一般說的 synchronized 用來做多線程同步功能,其實(shí)synchronized 只是提供多線程互斥,而對(duì)象的 wait()和 notify()方法才提供線程的同步功能。
JVM 通過 Monitor 對(duì)象實(shí)現(xiàn)線程同步,當(dāng)多個(gè)線程同時(shí)請(qǐng)求synchronized 方法或塊時(shí),monitor 會(huì)設(shè)置幾個(gè)虛擬邏輯數(shù)據(jù)結(jié)構(gòu)來管理這些多線程。新請(qǐng)求的線程會(huì)首先被加入到線程排隊(duì)隊(duì)列中,線程阻塞,當(dāng)某個(gè)擁有鎖的線程 unlock 之后,則排隊(duì)隊(duì)列里的線程競(jìng)爭(zhēng)上崗(synchronized 是不公平競(jìng)爭(zhēng)鎖,下面還會(huì)講到)。如果運(yùn)行的線程調(diào)用對(duì)象的 wait()后就釋放鎖并進(jìn)入 wait線程集合那邊,當(dāng)調(diào)用對(duì)象的 notify()
或 notifyall()
后,wait 線程就到排隊(duì)那邊。這是大致的邏輯。
HashMap
線程不安全(hash碰撞與擴(kuò)容導(dǎo)致)HashMap
的底層存儲(chǔ)結(jié)構(gòu)是一個(gè) Entry 數(shù)組,每個(gè) Entry 又是一個(gè)單鏈表,一旦發(fā)生 Hash 沖突的的時(shí)候,HashMap
采用拉鏈法解決碰撞沖突,因?yàn)?nbsp;hashMap
的put 方法不是同步的,所以他的擴(kuò)容方法也不是同步的,在擴(kuò)容過程中,會(huì)新生成一個(gè)新的容量的數(shù)組,然后對(duì)原數(shù)組的所有鍵值對(duì)重新進(jìn)行計(jì)算和寫入新的數(shù)組,之后指向新生成的數(shù)組。當(dāng)多個(gè)線程同時(shí)檢測(cè)到 hashmap
需要擴(kuò)容的時(shí)候就會(huì)同時(shí)調(diào)用 resize 操作,各自生成新的數(shù)組并 rehash 后賦給該 map 底層的數(shù)組 table,結(jié)果最終只有最后一個(gè)線程生成的新數(shù)組被賦給 table 變量,其他線程的均會(huì)丟失。而且當(dāng)某些線程已經(jīng)完成賦值而其他線程剛開始的時(shí)候,就會(huì)用已經(jīng)被賦值的 table 作為原始數(shù)組,這樣也會(huì)有問題。擴(kuò)容的時(shí)候 可能會(huì)引發(fā)鏈表形成環(huán)狀結(jié)構(gòu)
(1)ArrayList
: ArrayList
是一個(gè)泛型類,底層采用數(shù)組結(jié)構(gòu)保存對(duì)象。數(shù)組結(jié)構(gòu)的優(yōu)點(diǎn)是便于對(duì)集合進(jìn)行快速的隨機(jī)訪問,即如果需要經(jīng)常根據(jù)索引位置訪問集合中的對(duì)象,使用由 ArrayList
類實(shí)現(xiàn)的 List 集合的效率較好。數(shù)組結(jié)構(gòu)的缺點(diǎn)是向指定索引位置插入對(duì)象和刪除指定索引位置對(duì)象的速度較慢,并且插入或刪除對(duì)象的索引位置越小效率越低,原因是當(dāng)向指定的索引位置插入對(duì)象時(shí),會(huì)同時(shí)將指定索引位置及之后的所有對(duì)象相應(yīng)的向后移動(dòng)一位。
(2)LinkedList
: LinkedList
是一個(gè)泛型類,底層是一個(gè)雙向鏈表,所以它在執(zhí)行插入和刪除操作時(shí)比 ArrayList 更加的高效,但也因?yàn)殒湵淼臄?shù)據(jù)結(jié)構(gòu),所以在隨機(jī)訪問方面要比 ArrayList
差。
ArrayList
是線性表(數(shù)組)get()
直接讀取第幾個(gè)下標(biāo),復(fù)雜度 O(1)add(E)
添加元素,直接在后面添加,復(fù)雜度 O(1)add(index, E)
添加元素,在第幾個(gè)元素后面插入,后面的元素需要向后移動(dòng),復(fù)雜度 O(n)remove()
刪除元素,后面的元素需要逐個(gè)移動(dòng),復(fù)雜度 O(n)LinkedList
是鏈表的操作get()
獲取第幾個(gè)元素,依次遍歷,復(fù)雜度 O(n)add(E)
添加到末尾,復(fù)雜度 O(1)add(index, E)
添加第幾個(gè)元素后,需要先查找到第幾個(gè)元素,直接指針指向操作,復(fù)雜度 O(n)remove()
刪除元素,直接指針指向操作,復(fù)雜度 O(1)
1.地址空間: 同一進(jìn)程的線程共享本進(jìn)程的地址空間,而進(jìn)程之間則是獨(dú)立的地址空間。
2.資源擁有: 同一進(jìn)程內(nèi)的線程共享本進(jìn)程的資源如內(nèi)存、I/O、cpu 等,但是進(jìn)程之間的資源是獨(dú)立的。
3.一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其他進(jìn)程產(chǎn)生影響,但是一個(gè)線程崩潰整個(gè)進(jìn)程都死掉。所以多進(jìn)程要比多線程健壯
4.進(jìn)程切換時(shí),消耗的資源大,效率不高。所以涉及到頻繁的切換時(shí),使用線程要好于進(jìn)程。同樣如果要求同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線程不能用進(jìn)程
5.執(zhí)行過程: 每個(gè)獨(dú)立的進(jìn)程程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序入口。但是線程不能獨(dú)立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線程執(zhí)行控制。
6.線程是處理器調(diào)度的基本單位,但是進(jìn)程不是。
7.兩者均可并發(fā)執(zhí)行。
相比其他的 IPC 通信,比如消息機(jī)制、共享內(nèi)存、管道、信號(hào)量等,Binder 僅需一次內(nèi)存拷貝,即可讓目標(biāo)進(jìn)程讀取到更新數(shù)據(jù),同共享內(nèi)存一樣相當(dāng)高效,其他的 IPC 通信機(jī)制大多需要 2 次內(nèi)存拷貝。
Binder 內(nèi)存拷貝的原理為: 進(jìn)程 A 為Binder 客戶端,在 IPC 調(diào)用前,需將其用戶空間的數(shù)據(jù)拷貝到 Binder 驅(qū)動(dòng)的內(nèi)核空間,由于進(jìn)程 B 在打開 Binder 設(shè)備(/dev/binder)時(shí),已將 Binder 驅(qū)動(dòng)的內(nèi)核空間映射(mmap)到自己的進(jìn)程空間,所以進(jìn)程 B 可以直接看到 Binder 驅(qū)動(dòng)內(nèi)核空間的內(nèi)容改動(dòng)
1.發(fā)送方進(jìn)程通過系統(tǒng)調(diào)用(copy_from_user)將要發(fā)送的數(shù)據(jù)存拷貝到內(nèi)核緩存區(qū)中。
2.接收方開辟一段內(nèi)存空間,內(nèi)核通過系統(tǒng)調(diào)用(copy_to_user)將內(nèi)核緩存區(qū)中的數(shù)據(jù)拷貝到接收方的內(nèi)存緩存區(qū)。
幾種傳統(tǒng) IPC 機(jī)制存在 2 個(gè)問題:
1.需要進(jìn)行 2 次數(shù)據(jù)拷貝,第 1 次是從發(fā)送方用戶空間拷貝到內(nèi)核緩存區(qū),第 2次是從內(nèi)核緩存區(qū)拷貝到接收方用戶空間。
2.接收方進(jìn)程不知道事先要分配多大的空間來接收數(shù)據(jù),可能存在空間上的浪費(fèi)。
Java 內(nèi)存模型(即 Java Memory Model,簡(jiǎn)稱 JMM)本身是一種抽象的概念,并不真實(shí)存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個(gè)變量(包括實(shí)例字段,靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素)的訪問方式。由于 JVM 運(yùn)行程序的實(shí)體是線程,而每個(gè)線程創(chuàng)建時(shí) JVM 都會(huì)為其創(chuàng)建一個(gè)工作內(nèi)存(有些地方稱為棧空間),用于存儲(chǔ)線程私有的數(shù)據(jù),而 Java 內(nèi)存模型中規(guī)定所有變量都存儲(chǔ)在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問,但線程對(duì)變量的操作(讀取賦值等)必須在工作內(nèi)存中進(jìn)行,首先要將變量從主內(nèi)存拷貝的自己的工作內(nèi)存空間,然后對(duì)變量進(jìn)行操作,操作完成后再將變量寫回主內(nèi)存,不能直接操作主內(nèi)存中的變量,工作內(nèi)存中存儲(chǔ)著主內(nèi)存中的變量副本拷貝,前面說過,工作內(nèi)存是每個(gè)線程的私有數(shù)據(jù)區(qū)域,因此不同的線程間無法訪問對(duì)方的工作內(nèi)存,線程間的通信(傳值)必須通過主內(nèi)存來完成
類加載過程主要包含加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用、卸載七個(gè)方面,下面一一闡述。
1.加載: 獲取定義此類的二進(jìn)制字節(jié)流,生成這個(gè)類的 java.lang.Class 對(duì)象
2.驗(yàn)證: 保證 Class 文件的字節(jié)流包含的信息符合 JVM 規(guī)范,不會(huì)給 JVM 造成危害
3.準(zhǔn)備: 準(zhǔn)備階段為變量分配內(nèi)存并設(shè)置類變量的初始化
4.解析: 解析過程是將常量池內(nèi)的符號(hào)引用替換成直接引用
5.初始化: 不同于準(zhǔn)備階段,本次初始化,是根據(jù)程序員通過程序制定的計(jì)劃去初始化類的變量和其他資源。這些資源有 static{}塊,構(gòu)造函數(shù),父類的初始化等
6.使用:使用過程就是根據(jù)程序定義的行為執(zhí)行
7.卸載: 卸載由 GC 完成
1、 遇到
new
,getstatic
,putstatic
,invokestatic
這 4 條指令;
2、 使用java.lang.reflect
包的方法對(duì)類進(jìn)行反射調(diào)用;
3、 初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類沒有進(jìn)行過初始化,則先初始化其父類(注意!如果其父類是接口的話,則不要求初始化父類);
4、 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含 main 方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類;
5、 當(dāng)使用 jdk1.7 的動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle
實(shí)例最后的解析結(jié)果REF_getstatic
,REF_putstatic
,REF_invokeStatic
的方法句柄,并且這個(gè)方法句柄所對(duì)應(yīng)的類沒有進(jìn)行過初始化,則先觸發(fā)其類初始化
類加載器查找 class 所采用的是雙親委托模式,所謂雙親委托模式就是判斷該類是否已經(jīng)加載,如果沒有則不是自身去查找而是委托給父加載器進(jìn)行查找,這樣依次進(jìn)行遞歸,直到委托到最頂層的 Bootstrap ClassLoader
,如果 Bootstrap ClassLoader
找到了該 Class,就會(huì)直接返回,如果沒找到,則繼續(xù)依次向下查找,如果還沒找到則最后交給自身去查找
1. 避免重復(fù)加載,如果已經(jīng)加載過一次 Class,則不需要再次加載,而是直接讀取已經(jīng)加載的 Class
2. 更加安全,確保,java 核心 api 中定義類型不會(huì)被隨意替換,比如,采用雙親委托模式可以使得系統(tǒng)在 Java 虛擬機(jī)啟動(dòng)時(shí)舊加載了 String 類,也就無法用自定義的 String 類來替換系統(tǒng)的 String 類,這樣便可以防止核心 API 庫被隨意篡改。
死鎖的四個(gè)必要條件:
1.互斥條件: 一個(gè)資源每次只能被一個(gè)進(jìn)程使用
2.請(qǐng)求與保持條件: 進(jìn)程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源 已被其他進(jìn)程占有,此時(shí)請(qǐng)求進(jìn)程被阻塞,但對(duì)自己已獲得的資源保持不放。
3.不可剝奪條件: 進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走,即只能 由獲得該資源的進(jìn)程自己來釋放(只能是主動(dòng)釋放)。
4.循環(huán)等待條件: 若干進(jìn)程間形成首尾相接循環(huán)等待資源的關(guān)系
這四個(gè)條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會(huì)發(fā)生死鎖。
避免死鎖的方法: 系統(tǒng)對(duì)進(jìn)程發(fā)出每一個(gè)系統(tǒng)能夠滿足的資源申請(qǐng)進(jìn)行動(dòng)態(tài)檢查,并根據(jù)檢查結(jié)果決定是否分配資源,如果分配后系統(tǒng)可能發(fā)生死鎖,則不予分配,否則予以分配,這是一種保證系統(tǒng)不進(jìn)入死鎖狀態(tài)的動(dòng)態(tài)策略。
在資源的動(dòng)態(tài)分配過程中,用某種方法去防止系統(tǒng)進(jìn)入不安全狀態(tài),從而避免發(fā)生死鎖。 一般來說互斥條件是無法破壞的,所以在預(yù)防死鎖時(shí)主要從其他三個(gè)方面入手 :
(1)破壞請(qǐng)求和保持條件: 在系統(tǒng)中不允許進(jìn)程在已獲得某種資源的情況下,申請(qǐng)其他資源,即要想出一個(gè)辦法,阻止進(jìn)程在持有資源的同時(shí)申請(qǐng)其它資源。
方法一: 在所有進(jìn)程開始運(yùn)行之前,必須一次性的申請(qǐng)其在整個(gè)運(yùn)行過程中所需的全部資源,
方法二: 要求每個(gè)進(jìn)程提出新的資源申請(qǐng)前,釋放它所占有的資源
(2)破壞不可搶占條件: 允許對(duì)資源實(shí)行搶奪。
方式一: 如果占有某些資源的一個(gè)進(jìn)程進(jìn)行進(jìn)一步資源請(qǐng)求被拒絕,則該進(jìn)程必須釋放它最初占有的資源,如果有必要,可再次請(qǐng)求這些資源和另外的資源。
方式二: 如果一個(gè)進(jìn)程請(qǐng)求當(dāng)前被另一個(gè)進(jìn)程占有的資源,則操作系統(tǒng)可以搶占另一個(gè)進(jìn)程,要求它釋放資源,只有在任意兩個(gè)進(jìn)程的優(yōu)先級(jí)都不相同的條件下,該方法才能預(yù)防死鎖。
(3)破壞循環(huán)等待條件
對(duì)系統(tǒng)所有資源進(jìn)行線性排序并賦予不同的序號(hào),這樣我們便可以規(guī)定進(jìn)程在申請(qǐng)資源時(shí)必須按照序號(hào)遞增的順序進(jìn)行資源的申請(qǐng),當(dāng)以后要申請(qǐng)時(shí)需檢查要申請(qǐng)的資源的編號(hào)大于當(dāng)前編號(hào)時(shí),才能進(jìn)行申請(qǐng)。
利用算法避免死鎖:
所謂銀行家算法,是指在分配資源之前先看清楚,資源分配后是否會(huì)導(dǎo)致系統(tǒng)死鎖。如果會(huì)死鎖,則不分配,否則就分配。按照銀行家算法的思想,當(dāng)進(jìn)程請(qǐng)求資源時(shí),系統(tǒng)將按如下原則分配系統(tǒng)資源
App 啟動(dòng)時(shí),AMS
會(huì)檢查這個(gè)應(yīng)用程序所需要的進(jìn)程是否存在,不存在就會(huì)請(qǐng)求Zygote 進(jìn)程啟動(dòng)需要的應(yīng)用程序進(jìn)程,Zygote 進(jìn)程接收到 AMS 請(qǐng)求并通過 fock
自身創(chuàng)建應(yīng)用程序進(jìn)程,這樣應(yīng)用程序進(jìn)程就會(huì)獲取虛擬機(jī)的實(shí)例,還會(huì)創(chuàng)建Binder 線程池(ProcessState
.startThreadPool()
)和消息循環(huán)(ActivityThread looper.loop
),然后 App 進(jìn)程,通過 Binder IPC 向sytem_server
進(jìn)程發(fā)起attachApplication
請(qǐng)求;system_server 進(jìn)程在收到請(qǐng)求后,進(jìn)行一系列準(zhǔn)備工作后,再通過 Binder IPC 向 App 進(jìn)程發(fā)送scheduleLaunchActivity
請(qǐng)求;App 進(jìn)程的binder (ApplicationThread
)在收到請(qǐng)求后,通過 handler 向主線程發(fā)送LAUNCH_ACTIVITY 消息;主線程在收到 Message 后,通過反射機(jī)制創(chuàng)建目標(biāo)Activity,并回調(diào) Activity.onCreate()
等方法。到此,App 便正式啟動(dòng),開始進(jìn)入Activity 生命周期,執(zhí)行完 onCreate
/onStart
/onResume
方法,UI 渲染結(jié)束后便可以看到 App 的主界面。
HashMap
如何保證元素均勻分布hash & (length-1)
通過 Key 值的 hashCode
值和 hashMap
長(zhǎng)度-1 做與運(yùn)算hashmap
中的元素,默認(rèn)情況下,數(shù)組大小為 16,也就是 2 的 4 次方,如果要自定義 HashMap
初始化數(shù)組長(zhǎng)度,也要設(shè)置為 2 的 n 次方大小,因?yàn)檫@樣效率最高。因?yàn)楫?dāng)數(shù)組長(zhǎng)度為 2 的 n 次冪的時(shí)候,不同的 key 算出的 index 相同的幾率較小,那么數(shù)據(jù)在數(shù)組上分布就比較均勻,也就是說碰撞的幾率小,相對(duì)的,查詢的時(shí)候就不用遍歷某個(gè)位置上的鏈表,這樣查詢效率也就較高了
Android 單線程模型的核心原則就是: 只能在 UI 線程(Main Thread)中對(duì) UI 進(jìn)行處理。當(dāng)一個(gè)程序第一次啟動(dòng)時(shí),Android 會(huì)同時(shí)啟動(dòng)一個(gè)對(duì)應(yīng)的 主線程(Main Thread),主線程主要負(fù)責(zé)處理與 UI 相關(guān)的事件,如:用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事 件,并把相關(guān)的事件分發(fā)到對(duì)應(yīng)的組件進(jìn)行處理。所以主線程通常又被叫做 UI 線 程。在開發(fā) Android 應(yīng)用時(shí)必須遵守單線程模型的原則: Android UI 操作并不是線程安全的并且這些操作必須在 UI 線程中執(zhí)行。
Android 的單線程模型有兩條原則:
1.不要阻塞 UI 線程。
2.不要在 UI 線程之外訪問 Android UI toolkit(主要是這兩個(gè)包中的組件:android.widget and android.view
RecyclerView
在很多方面能取代 ListView
,Google 為什么沒把ListView
劃上一條過時(shí)的橫線?ListView
采用的是 RecyclerBin
的回收機(jī)制在一些輕量級(jí)的 List 顯示時(shí)效率更高。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。