溫馨提示×

溫馨提示×

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

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

大廠都愛問到多線程?27道關(guān)于多線程的總結(jié)助你一臂之力

發(fā)布時間:2020-09-13 10:28:02 來源:網(wǎng)絡(luò) 閱讀:152 作者:Android丶VG 欄目:移動開發(fā)

本文主要是整理了中高級安卓需要會多線程模塊的(或者說面試被頻繁問到的內(nèi)容),主要作為參考大綱,之后會陸續(xù)更新每個詳細部分,供大家參考,互相學習。

面試板塊(PDF版如下):

BAT面試合集(Binder,組件化插件化,熱修復,AOP,QQ換膚,虛擬機,https,線程池原理,音視頻原理)
算法合集(Hash,KMP 等)
中小廠面試合集(內(nèi)存泄漏,Handler,View,MVC.MVP.MVVM,)
大廠相關(guān)更新技術(shù)(Glide,數(shù)據(jù)庫,NDK)
面試小知識(java小知識)
設(shè)計模式(設(shè)計模式原則和分類)
數(shù)據(jù)結(jié)構(gòu)(數(shù)據(jù)結(jié)構(gòu)等等)
網(wǎng)絡(luò)編程(三次握手和四次握手,Volley,OKHttps,Retrofit)
源碼解析(屬性動畫實現(xiàn)原理等)
多線程解析(線程同步,進程線程)
性能優(yōu)化(Webview,內(nèi)存泄漏和內(nèi)存溢出等)

大廠都愛問到多線程?27道關(guān)于多線程的總結(jié)助你一臂之力
順手留下GitHub鏈接,需要獲取相關(guān)面試或者面試寶典核心筆記PDF等內(nèi)容的可以自己去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)

一丶 什么是線程

線程就是進程中運行的多個子任務(wù),是操作系統(tǒng)調(diào)用的最小單元

二丶線程的狀態(tài)

New: 新建狀態(tài),new 出來,還沒有調(diào)用 start
Runnable: 可運行狀態(tài),調(diào)用 start 進入可運行狀態(tài),可能運行也可能沒有運行,取決于操作系統(tǒng)的調(diào)度
Blocked: 阻塞狀態(tài),被鎖阻塞,暫時不活動,阻塞狀態(tài)是線程阻塞在進入synchronized 關(guān)鍵字修飾的方法或代碼塊(獲取鎖)時的狀態(tài)。
Waiting: 等待狀態(tài),不活動,不運行任何代碼,等待線程調(diào)度器調(diào)度,wait sleep
Timed Waiting: 超時等待,在指定時間自行返回
Terminated: 終止狀態(tài),包括正常終止和異常終止

三丶線程的創(chuàng)建

a.繼承 Thread 重寫 run 方法
b.實現(xiàn) Runnable 重寫 run 方法
c.實現(xiàn) Callable 重寫 call 方法
實現(xiàn) Callable 和實現(xiàn) Runnable 類似,但是功能更強大,具體表現(xiàn)在
a.可以在任務(wù)結(jié)束后提供一個返回值,Runnable 不行
b.call 方法可以拋出異常,Runnable 的 run 方法不行
c.可以通過運行 Callable 得到的 Fulture 對象監(jiān)聽目標線程調(diào)用 call 方法的結(jié)果,得到返回值,(fulture.get(),調(diào)用后會阻塞,直到獲取到返回值)

四丶線程中斷

一般情況下,線程不執(zhí)行完任務(wù)不會退出,但是在有些場景下,我們需要手動控制線程中斷結(jié)束任務(wù),Java 中有提供線程中斷機制相關(guān)的 Api,每個線程都一個狀態(tài)位用于標識當前線程對象是否是中斷狀態(tài)

public boolean isInterrupted() //判斷中斷標識位是否是 true,不會改變標
識位 public void interrupt() //將中斷標識位設(shè)置為 truepublic static<br/>boolean interrupted() //判斷當前線程是否被中斷,并且該方法調(diào)用結(jié)束的時候會清空中斷標識位需要注意的是 interrupt()方法并不會真的中斷線程,它只是將中斷標識位設(shè)置為 true,具體是否要中斷由程序來判斷,如下,只要線程中斷標識位為 false,也就是沒有中斷就一直執(zhí)行線程方法

  new Thread( new Runnable(){
          while (!Thread.currentThread().isInterrupted()){
                  //執(zhí)行線程方法
          }
  }).start();

前邊我們提到了線程的六種狀態(tài),New Runnable Blocked Waiting Timed WaitingTerminated,那么在這六種狀態(tài)下調(diào)用線程中斷的代碼會怎樣呢,New 和Terminated 狀態(tài)下,線程不會理會線程中斷的請求,既不會設(shè)置標記位,在Runnable 和 Blocked 狀態(tài)下調(diào)用 interrupt 會將標志位設(shè)置位 true,在 Waiting 和Timed Waiting 狀態(tài)下會發(fā)生 InterruptedException 異常,針對這個異常我們?nèi)绾翁幚恚?/p>

1.在 catch 語句中通過 interrupt 設(shè)置中斷狀態(tài),因為發(fā)生中斷異常時,中斷標志位會被復位,我們需要重新將中斷標志位設(shè)置為 true,這樣外界可以通過這個狀態(tài)判斷是否需要中斷線程

  try {
  ....
  } catch (InterruptedException e){
    Thread.currentThread().interrupt();
  }

2.更好的做法是,不捕獲異常,直接拋出給調(diào)用者處理,這樣更靈活

五丶Thread為什么不能用stop方法停止線程

從 SUN 的官方文檔可以得知,調(diào)用 Thread.stop()方法是不安全的,這是因為當調(diào)用 Thread.stop()方法時,會發(fā)生下面兩件事:

  1. 即刻拋出 ThreadDeath 異常,在線程的 run()方法內(nèi),任何一點都有可能拋出ThreadDeath Error,包括在 catch 或 finally 語句中。
  2. 釋放該線程所持有的所有的鎖。調(diào)用 thread.stop()后導致了該線程所持有的所有鎖的突然釋放,那么被保護數(shù)據(jù)就有可能呈現(xiàn)不一致性,其他線程在使用這些被破壞的數(shù)據(jù)時,有可能導致一些很奇怪的應用程序錯誤。
六丶 重入鎖與條件對象,同步方法和同步代碼塊

。。。

七丶volatile 關(guān)鍵字

volatile 為實例域的同步訪問提供了免鎖機制,如果聲明一個域為volatile,那么編譯器和虛擬機就直到該域可能被另一個線程并發(fā)更新

八丶 java內(nèi)存模型

堆內(nèi)存是被所有線程共享的運行時內(nèi)存區(qū)域,存在可見性的問題。線程之間共享變量存儲在主存中,每個線程都有一個私有的本地內(nèi)存,本地內(nèi)存存儲了該線程共享變量的副本(本地內(nèi)存是一個抽象概念,并不真實存在),兩個線程要通信的話,首先 A 線程把本地內(nèi)存更新過的共享變量更新到主存中,然后 B 線程去主存中讀取 A 線程更新過的共享變量,也就是說假設(shè)線程 A 執(zhí)行了 i = 1 這行代碼更新主線程變量 i 的值,會首先在自己的工作線程中堆變量 i 進行賦值,然后再寫入主存當中,而不是直接寫入主存

九丶原子性 性 可見性 有序性

原子性: 對基本數(shù)據(jù)類型的讀取和賦值操作是原子性操作,這些操作不可被中斷,是一步到位的,例如 x=3 是原子性操作,而 y = x 就不是,
它包含兩步: 第一讀取 x,第二將 x 寫入工作內(nèi)存;x++也不是原子性操作,它包含三部,第一,讀取x,第二,對 x 加 1,第三,寫入內(nèi)存。
原子性操作的類如: AtomicInteger AtomicBoolean AtomicLong AtomicReference

可見性: 指線程之間的可見性,既一個線程修改的狀態(tài)對另一個線程是可見的。volatile 修飾可以保證可見性,它會保證修改的值會立即被更新到主存,所以對其他線程是可見的,普通的共享變量不能保證可見性,因為被修改后不會立即寫入主存,何時被寫入主存是不確定的,所以其他線程去讀取的時候可能讀到的還是舊值

有序性: Java 中的指令重排序(包括編譯器重排序和運行期重排序)可以起到優(yōu)化代碼的作用,但是在多線程中會影響到并發(fā)執(zhí)行的正確性,使用 volatile 可以保證有序性,禁止指令重排

volatile 可以保證可見性 有序性,但是無法保證原子性,在某些情況下可以提供優(yōu)于鎖的性能和伸縮性,替代 sychronized 關(guān)鍵字簡化代碼,但是要嚴格遵循使用條件。

10. 線程池 ThreadPoolExecutor

線程池的工作原理: 線程池可以減少創(chuàng)建和銷毀線程的次數(shù),從而減少系統(tǒng)資源的消耗,當一個任務(wù)提交到線程池時

a. 首先判斷核心線程池中的線程是否已經(jīng)滿了,如果沒滿,則創(chuàng)建一個核心線程執(zhí)行任務(wù),否則進入下一步
b. 判斷工作隊列是否已滿,沒有滿則加入工作隊列,否則執(zhí)行下一步
c. 判斷線程數(shù)是否達到了最大值,如果不是,則創(chuàng)建非核心線程執(zhí)行任務(wù),否則執(zhí)行飽和策略,默認拋出異常

十一丶 線程池的種類

1.FixedThreadPool: 可重用固定線程數(shù)的線程池,只有核心線程,沒有非核心線程,核心線程不會被回收,有任務(wù)時,有空閑的核心線程就用核心線程執(zhí)行,沒有則加入隊列排隊
2.SingleThreadExecutor: 單線程線程池,只有一個核心線程,沒有非核心線程,當任務(wù)到達時,如果沒有運行線程,則創(chuàng)建一個線程執(zhí)行,如果正在運行則加入隊列等待,可以保證所有任務(wù)在一個線程中按照順序執(zhí)行,和 FixedThreadPool 的區(qū)別只有數(shù)量
3.CachedThreadPool: 按需創(chuàng)建的線程池,沒有核心線程,非核心線程有Integer.MAX_VALUE 個,每次提交任務(wù)如果有空閑線程則由空閑線程執(zhí)行,沒有空閑線程則創(chuàng)建新的線程執(zhí)行,適用于大量的需要立即處理的并且耗時較短的任務(wù)
4.ScheduledThreadPoolExecutor: 繼承自 ThreadPoolExecutor,用于延時執(zhí)行任務(wù)或定期執(zhí)行任務(wù),核心線程數(shù)固定,線程總數(shù)為Integer.MAX_VALUE

十二丶線程同步機制與原理

為什么需要線程同步?當多個線程操作同一個變量的時候,存在這個變量何時對另一個線程可見的問題,也就是可見性。每一個線程都持有主存中變量的一個副本,當他更新這個變量時,首先更新的是自己線程中副本的變量值,然后會將這個值更新到主存中,但是是否立即更新以及更新到主存的時機是不確定的這就導致當另一個線程操作這個變量的時候,他從主存中讀取的這個變量還是舊的值,導致兩個線程不同步的問題。

線程同步就是為了保證多線程操作的可見性和原子性,比如我們用synchronized 關(guān)鍵字包裹一端代碼,我們希望這段碼執(zhí)行完成后,對另一個線程立即可見,另一個線程再次操作的時候得到的是上一個線程更新之后的內(nèi)容,還有就是保證這段代碼的原子性,這段代碼可能涉及到了好幾部操作,我們希望這好幾步的操作一次完成不會被中間打斷,鎖的同步機制就可以實現(xiàn)這一點。一般說的 synchronized 用來做多線程同步功能,其實synchronized 只是提供多線程互斥,而對象的 wait()和 notify()方法才提供線程的同步功能。

JVM 通過 Monitor 對象實現(xiàn)線程同步,當多個線程同時請求synchronized 方法或塊時,monitor 會設(shè)置幾個虛擬邏輯數(shù)據(jù)結(jié)構(gòu)來管理這些多線程。新請求的線程會首先被加入到線程排隊隊列中,線程阻塞,當某個擁有鎖的線程 unlock 之后,則排隊隊列里的線程競爭上崗(synchronized 是不公平競爭鎖,下面還會講到)。如果運行的線程調(diào)用對象的 wait()后就釋放鎖并進入 wait線程集合那邊,當調(diào)用對象的 notify()notifyall()后,wait 線程就到排隊那邊。這是大致的邏輯。

十三丶arrayList 與 linkedList 的讀寫時間復雜度

(1)ArrayList ArrayList 是一個泛型類,底層采用數(shù)組結(jié)構(gòu)保存對象。數(shù)組結(jié)構(gòu)的優(yōu)點是便于對集合進行快速的隨機訪問,即如果需要經(jīng)常根據(jù)索引位置訪問集合中的對象,使用由 ArrayList 類實現(xiàn)的 List 集合的效率較好。數(shù)組結(jié)構(gòu)的缺點是向指定索引位置插入對象和刪除指定索引位置對象的速度較慢,并且插入或刪除對象的索引位置越小效率越低,原因是當向指定的索引位置插入對象時,會同時將指定索引位置及之后的所有對象相應的向后移動一位。

(2)LinkedList LinkedList 是一個泛型類,底層是一個雙向鏈表,所以它在執(zhí)行插入和刪除操作時比 ArrayList 更加的高效,但也因為鏈表的數(shù)據(jù)結(jié)構(gòu),所以在隨機訪問方面要比 ArrayList 差。

ArrayList 是線性表(數(shù)組)
get()直接讀取第幾個下標,復雜度 O(1)
add(E)添加元素,直接在后面添加,復雜度 O(1)
add(index, E)添加元素,在第幾個元素后面插入,后面的元素需要向后移動,復雜度 O(n)
remove()刪除元素,后面的元素需要逐個移動,復雜度 O(n)LinkedList是鏈表的操作
get()獲取第幾個元素,依次遍歷,復雜度 O(n)
add(E) 添加到末尾,復雜度 O(1)
add(index, E) 添加第幾個元素后,需要先查找到第幾個元素,直接指針指向操作,復雜度 O(n)
remove()刪除元素,直接指針指向操作,復雜度 O(1)

十四丶 為什么HashMap 線程不安全(hash碰撞與擴容導致)

HashMap 的底層存儲結(jié)構(gòu)是一個 Entry 數(shù)組,每個 Entry 又是一個單鏈表,一旦發(fā)生 Hash 沖突的的時候,HashMap 采用拉鏈法解決碰撞沖突,因為 hashMap 的put 方法不是同步的,所以他的擴容方法也不是同步的,在擴容過程中,會新生成一個新的容量的數(shù)組,然后對原數(shù)組的所有鍵值對重新進行計算和寫入新的數(shù)組,之后指向新生成的數(shù)組。當多個線程同時檢測到 hashmap 需要擴容的時候就會同時調(diào)用 resize 操作,各自生成新的數(shù)組并 rehash 后賦給該 map 底層的數(shù)組 table,結(jié)果最終只有最后一個線程生成的新數(shù)組被賦給 table 變量,其他線程的均會丟失。而且當某些線程已經(jīng)完成賦值而其他線程剛開始的時候,就會用已經(jīng)被賦值的 table 作為原始數(shù)組,這樣也會有問題。擴容的時候 可能會引發(fā)鏈表形成環(huán)狀結(jié)構(gòu)

十五丶 進程線程的區(qū)別

1.地址空間: 同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。
2.資源擁有: 同一進程內(nèi)的線程共享本進程的資源如內(nèi)存、I/O、cpu 等,但是進程之間的資源是獨立的。
3.一個進程崩潰后,在保護模式下不會對其他進程產(chǎn)生影響,但是一個線程崩潰整個進程都死掉。所以多進程要比多線程健壯
4.進程切換時,消耗的資源大,效率不高。所以涉及到頻繁的切換時,使用線程要好于進程。同樣如果要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程不能用進程
5.執(zhí)行過程: 每個獨立的進程程有一個程序運行的入口、順序執(zhí)行序列和程序入口。但是線程不能獨立執(zhí)行,必須依存在應用程序中,由應用程序提供多個線程執(zhí)行控制。
6.線程是處理器調(diào)度的基本單位,但是進程不是。
7.兩者均可并發(fā)執(zhí)行。

十六丶Binder 的內(nèi)存拷貝過程

相比其他的 IPC 通信,比如消息機制、共享內(nèi)存、管道、信號量等,Binder 僅需一次內(nèi)存拷貝,即可讓目標進程讀取到更新數(shù)據(jù),同共享內(nèi)存一樣相當高效,其他的 IPC 通信機制大多需要 2 次內(nèi)存拷貝。

Binder 內(nèi)存拷貝的原理為: 進程 A 為Binder 客戶端,在 IPC 調(diào)用前,需將其用戶空間的數(shù)據(jù)拷貝到 Binder 驅(qū)動的內(nèi)核空間,由于進程 B 在打開 Binder 設(shè)備(/dev/binder)時,已將 Binder 驅(qū)動的內(nèi)核空間映射(mmap)到自己的進程空間,所以進程 B 可以直接看到 Binder 驅(qū)動內(nèi)核空間的內(nèi)容改動

十七丶傳統(tǒng) IPC 機制的通信原理(2次內(nèi)存拷貝)

1.發(fā)送方進程通過系統(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 機制存在 2 個問題:

1.需要進行 2 次數(shù)據(jù)拷貝,第 1 次是從發(fā)送方用戶空間拷貝到內(nèi)核緩存區(qū),第 2次是從內(nèi)核緩存區(qū)拷貝到接收方用戶空間。
2.接收方進程不知道事先要分配多大的空間來接收數(shù)據(jù),可能存在空間上的浪費。

十八丶Java內(nèi)存模型 (記住堆棧是內(nèi)存分區(qū),不是模型)

Java 內(nèi)存模型(即 Java Memory Model,簡稱 JMM)本身是一種抽象的概念,并不真實存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個變量(包括實例字段,靜態(tài)字段和構(gòu)成數(shù)組對象的元素)的訪問方式。由于 JVM 運行程序的實體是線程,而每個線程創(chuàng)建時 JVM 都會為其創(chuàng)建一個工作內(nèi)存(有些地方稱為??臻g),用于存儲線程私有的數(shù)據(jù),而 Java 內(nèi)存模型中規(guī)定所有變量都存儲在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內(nèi)存中進行,首先要將變量從主內(nèi)存拷貝的自己的工作內(nèi)存空間,然后對變量進行操作,操作完成后再將變量寫回主內(nèi)存,不能直接操作主內(nèi)存中的變量,工作內(nèi)存中存儲著主內(nèi)存中的變量副本拷貝,前面說過,工作內(nèi)存是每個線程的私有數(shù)據(jù)區(qū)域,因此不同的線程間無法訪問對方的工作內(nèi)存,線程間的通信(傳值)必須通過主內(nèi)存來完成

十九丶類的加載過程

類加載過程主要包含加載、驗證、準備、解析、初始化、使用、卸載七個方面,下面一一闡述。
1.加載: 獲取定義此類的二進制字節(jié)流,生成這個類的 java.lang.Class 對象
2.驗證: 保證 Class 文件的字節(jié)流包含的信息符合 JVM 規(guī)范,不會給 JVM 造成危害
3.準備: 準備階段為變量分配內(nèi)存并設(shè)置類變量的初始化
4.解析: 解析過程是將常量池內(nèi)的符號引用替換成直接引用
5.初始化: 不同于準備階段,本次初始化,是根據(jù)程序員通過程序制定的計劃去初始化類的變量和其他資源。這些資源有 static{}塊,構(gòu)造函數(shù),父類的初始化等
6.使用:使用過程就是根據(jù)程序定義的行為執(zhí)行
7.卸載: 卸載由 GC 完成

二十丶 什么情況下會觸發(fā)類的初始化

1、 遇到 newgetstatic,putstaticinvokestatic 這 4 條指令;
2、 使用 java.lang.reflect 包的方法對類進行反射調(diào)用;
3、 初始化一個類的時候,如果發(fā)現(xiàn)其父類沒有進行過初始化,則先初始化其父類(注意!如果其父類是接口的話,則不要求初始化父類);
4、 當虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類(包含 main 方法的那個類),虛擬機會先初始化這個主類;
5、 當使用 jdk1.7 的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果 REF_getstatic,REF_putstatic,REF_invokeStatic 的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則先觸發(fā)其類初始化

二十一丶雙親委托模式

類加載器查找 class 所采用的是雙親委托模式,所謂雙親委托模式就是判斷該類是否已經(jīng)加載,如果沒有則不是自身去查找而是委托給父加載器進行查找,這樣依次進行遞歸,直到委托到最頂層的 Bootstrap ClassLoader,如果 Bootstrap ClassLoader 找到了該 Class,就會直接返回,如果沒找到,則繼續(xù)依次向下查找,如果還沒找到則最后交給自身去查找

二十二丶雙親委托模式的好處

1. 避免重復加載,如果已經(jīng)加載過一次 Class,則不需要再次加載,而是直接讀取已經(jīng)加載的 Class
2. 更加安全,確保,java 核心 api 中定義類型不會被隨意替換,比如,采用雙親委托模式可以使得系統(tǒng)在 Java 虛擬機啟動時舊加載了 String 類,也就無法用自定義的 String 類來替換系統(tǒng)的 String 類,這樣便可以防止核心 API 庫被隨意篡改。

二十三丶死鎖的產(chǎn)生條件,如何避免死鎖

死鎖的四個必要條件
1.互斥條件: 一個資源每次只能被一個進程使用
2.請求與保持條件: 進程已經(jīng)保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他進程占有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。
3.不可剝奪條件: 進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能 由獲得該資源的進程自己來釋放(只能是主動釋放)。
4.循環(huán)等待條件: 若干進程間形成首尾相接循環(huán)等待資源的關(guān)系
這四個條件是死鎖的必要條件,只要系統(tǒng)發(fā)生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發(fā)生死鎖。

避免死鎖的方法: 系統(tǒng)對進程發(fā)出每一個系統(tǒng)能夠滿足的資源申請進行動態(tài)檢查,并根據(jù)檢查結(jié)果決定是否分配資源,如果分配后系統(tǒng)可能發(fā)生死鎖,則不予分配,否則予以分配,這是一種保證系統(tǒng)不進入死鎖狀態(tài)的動態(tài)策略。

在資源的動態(tài)分配過程中,用某種方法去防止系統(tǒng)進入不安全狀態(tài),從而避免發(fā)生死鎖。 一般來說互斥條件是無法破壞的,所以在預防死鎖時主要從其他三個方面入手 :
(1)破壞請求和保持條件: 在系統(tǒng)中不允許進程在已獲得某種資源的情況下,申請其他資源,即要想出一個辦法,阻止進程在持有資源的同時申請其它資源。
方法一: 在所有進程開始運行之前,必須一次性的申請其在整個運行過程中所需的全部資源,
方法二: 要求每個進程提出新的資源申請前,釋放它所占有的資源
(2)破壞不可搶占條件: 允許對資源實行搶奪。
方式一: 如果占有某些資源的一個進程進行進一步資源請求被拒絕,則該進程必須釋放它最初占有的資源,如果有必要,可再次請求這些資源和另外的資源。
方式二: 如果一個進程請求當前被另一個進程占有的資源,則操作系統(tǒng)可以搶占另一個進程,要求它釋放資源,只有在任意兩個進程的優(yōu)先級都不相同的條件下,該方法才能預防死鎖。
(3)破壞循環(huán)等待條件
對系統(tǒng)所有資源進行線性排序并賦予不同的序號,這樣我們便可以規(guī)定進程在申請資源時必須按照序號遞增的順序進行資源的申請,當以后要申請時需檢查要申請的資源的編號大于當前編號時,才能進行申請。
利用算法避免死鎖:
所謂銀行家算法,是指在分配資源之前先看清楚,資源分配后是否會導致系統(tǒng)死鎖。如果會死鎖,則不分配,否則就分配。按照銀行家算法的思想,當進程請求資源時,系統(tǒng)將按如下原則分配系統(tǒng)資源

二十四丶App啟動流程

App 啟動時,AMS 會檢查這個應用程序所需要的進程是否存在,不存在就會請求Zygote 進程啟動需要的應用程序進程,Zygote 進程接收到 AMS 請求并通過 fock自身創(chuàng)建應用程序進程,這樣應用程序進程就會獲取虛擬機的實例,還會創(chuàng)建Binder 線程池(ProcessState.startThreadPool())和消息循環(huán)(ActivityThread looper.loop),然后 App 進程,通過 Binder IPC 向sytem_server 進程發(fā)起attachApplication 請求;system_server 進程在收到請求后,進行一系列準備工作后,再通過 Binder IPC 向 App 進程發(fā)送scheduleLaunchActivity 請求;App 進程的binder (ApplicationThread)在收到請求后,通過 handler 向主線程發(fā)送LAUNCH_ACTIVITY 消息;主線程在收到 Message 后,通過反射機制創(chuàng)建目標Activity,并回調(diào) Activity.onCreate()等方法。到此,App 便正式啟動,開始進入Activity 生命周期,執(zhí)行完 onCreate/onStart/onResume 方法,UI 渲染結(jié)束后便可以看到 App 的主界面。

二十五丶Android 單線程模型

Android 單線程模型的核心原則就是: 只能在 UI 線程(Main Thread)中對 UI 進行處理。當一個程序第一次啟動時,Android 會同時啟動一個對應的 主線程(Main Thread),主線程主要負責處理與 UI 相關(guān)的事件,如:用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事 件,并把相關(guān)的事件分發(fā)到對應的組件進行處理。所以主線程通常又被叫做 UI 線 程。在開發(fā) Android 應用時必須遵守單線程模型的原則: Android UI 操作并不是線程安全的并且這些操作必須在 UI 線程中執(zhí)行。

Android 的單線程模型有兩條原則:

1.不要阻塞 UI 線程。
2.不要在 UI 線程之外訪問 Android UI toolkit(主要是這兩個包中的組件:android.widget and android.view

二十六丶RecyclerView 在很多方面能取代 ListView ,Google 為什么沒把ListView劃上一條過時的橫線?

ListView采用的是 RecyclerBin 的回收機制在一些輕量級的 List 顯示時效率更高。

二十七丶HashMap 如何保證元素均勻分布

hash & (length-1)
通過 Key 值的 hashCode 值和 hashMap長度-1 做與運算
hashmap中的元素,默認情況下,數(shù)組大小為 16,也就是 2 的 4 次方,如果要自定義 HashMap 初始化數(shù)組長度,也要設(shè)置為 2 的 n 次方大小,因為這樣效率最高。因為當數(shù)組長度為 2 的 n 次冪的時候,不同的 key 算出的 index 相同的幾率較小,那么數(shù)據(jù)在數(shù)組上分布就比較均勻,也就是說碰撞的幾率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了

順手留下GitHub鏈接,需要獲取相關(guān)面試等內(nèi)容的可以自己去找
https://github.com/xiangjiana/Android-MS

PDF獲取
向AI問一下細節(jié)

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

AI