2019Android多線程面試總結

極力推薦文章:歡迎收藏
Android 乾貨分享 java

閱讀五分鐘,每日十點,和您一塊兒終身學習,這裏是程序員Android

1.什麼是線程

線程就是進程中運行的多個子任務,是操做系統調用的最小單元android

2.線程的狀態

1.New:

新建狀態,new出來,尚未調用start程序員

2.Runnable:

可運行狀態,調用start進入可運行狀態,可能運行也可能沒有運行,取決於操做系統的調度算法

3.Blocked:

阻塞狀態,被鎖阻塞,暫時不活動,阻塞狀態是線程阻塞在進入api

4.synchronized:

關鍵字修飾的方法或代碼塊(獲取鎖)時的狀態。數組

5.Waiting:

等待狀態,不活動,不運行任何代碼,等待線程調度器調度,wait sleep用來暫停當前線程的執行,以毫秒爲單位,任何其它線程均可以中斷當前線程的睡眠,這種狀況下將拋出InterruptedException異常緩存

6.Timed Waiting:

超時等待,在指定時間自行返回安全

7.Terminated:

終止狀態,包括正常終止和異常終止微信

3.線程的建立

線程建立的經常使用方法
  • 1.繼承Thread重寫run方法
  • 2.實現Runnable重寫run方法
  • 3.實現Callable重寫call方法
實現Callable重寫call方法

實現Callable和實現Runnable相似,可是功能更強大,具體表如今數據結構

  • a.能夠在任務結束後提供一個返回值,Runnable不行
  • b.call方法能夠拋出異常,Runnablerun方法不行
  • c.能夠經過運行Callable獲得的Fulture對象監聽目標線程調用call方法的結果,獲得返回值,(fulture.get(),調用後會阻塞,直到獲取到返回值)

4.線程中斷

通常狀況下,線程不執行完任務不會退出,可是在有些場景下,咱們須要手動控制線程中斷結束任務,Java中有提供線程中斷機制相關的Api,每一個線程都一個狀態位用於標識當前線程對象是不是中斷狀態

public boolean isInterrupted() //判斷中斷標識位是不是true,不會改變標識位
public void interrupt()  //將中斷標識位設置爲true
public static boolean interrupted() //判斷當前線程是否被中斷,而且該方法調用結束的時候會清空中斷標識位

須要注意的是interrupt()方法並不會真的中斷線程,它只是將中斷標識位設置爲true,具體是否要中斷由程序來判斷,以下,只要線程中斷標識位爲false,也就是沒有中斷就一直執行線程方法

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

前邊咱們提到了線程的六種狀態,New 、Runnable、 Blocked、 Waiting、 Timed Waiting、 Terminated,那麼在這六種狀態下調用線程中斷的代碼會怎樣呢,NewTerminated狀態下,線程不會理會線程中斷的請求,既不會設置標記位,在RunnableBlocked狀態下調用interrupt會將標誌位設置位true,在WaitingTimed Waiting狀態下會發生InterruptedException異常,針對這個異常咱們如何處理?

  • 1.在catch語句中經過interrupt設置中斷狀態,由於發生中斷異常時,中斷標誌位會被複位,咱們須要從新將中斷標誌位設置爲true,這樣外界能夠經過這個狀態判斷是否須要中斷線程
try{
    ....
}catch(InterruptedException e){
    Thread.currentThread().interrupt();
}
  • 2.更好的作法是,不捕獲異常,直接拋出給調用者處理,這樣更靈活

5.Thread爲何不能用stop方法中止線程

SUN的官方文檔能夠得知,調用Thread.stop()方法是不安全的,這是由於當調用Thread.stop()方法時,會發生下面兩件事:

  • 1.即刻拋出 ThreadDeath異常,在線程的run()方法內,任何一點都有可能拋出ThreadDeath Error,包括在catchfinally語句中。
  • 2.釋放該線程所持有的全部的鎖。調用thread.stop()後致使了該線程所持有的全部鎖的忽然釋放,那麼被保護數據就有可能呈現不一致性,其餘線程在使用這些被破壞的數據時,有可能致使一些很奇怪的應用程序錯誤。

6.同步方法和同步代碼塊

爲什麼要使用同步?
java容許多線程併發控制,當多個線程同時操做一個可共享的資源變量時(如數據的增刪改查), 將會致使數據不許確,相互之間產生衝突,所以加入同步鎖以免在該線程沒有完成操做以前,被其餘線程的調用,從而保證了該變量的惟一性和準確性。

  • 1.同步方法

即有synchronized關鍵字修飾的方法, 因爲java的每一個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,須要得到內置鎖,不然就處於阻塞狀態。

代碼如:

public synchronized void save(){
   
    }

注: synchronized關鍵字也能夠修飾靜態方法,此時若是調用該靜態方法,將會鎖住整個類

  • 2.同步代碼塊

即有synchronized關鍵字修飾的語句塊,被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步

代碼如:

synchronized(object){ 
    }

注:同步是一種高開銷的操做,所以應該儘可能減小同步的內容。
一般沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼便可。

/**
     * 線程同步的運用
     * 
     * @author XIEHEJUN
     * 
     */
    public class SynchronizedThread {
 
        class Bank {
 
            private int account = 100;
 
            public int getAccount() {
                return account;
            }
 
            /**
             * 用同步方法實現
             * 
             * @param money
             */
            public synchronized void save(int money) {
                account += money;
            }
 
            /**
             * 用同步代碼塊實現
             * 
             * @param money
             */
            public void save1(int money) {
                synchronized (this) {
                    account += money;
                }
            }
        }
 
        class NewThread implements Runnable {
            private Bank bank;
 
            public NewThread(Bank bank) {
                this.bank = bank;
            }
 
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    // bank.save1(10);
                    bank.save(10);
                    System.out.println(i + "帳戶餘額爲:" + bank.getAccount());
                }
            }
 
        }
 
        /**
         * 創建線程,調用內部類
         */
        public void useThread() {
            Bank bank = new Bank();
            NewThread new_thread = new NewThread(bank);
            System.out.println("線程1");
            Thread thread1 = new Thread(new_thread);
            thread1.start();
            System.out.println("線程2");
            Thread thread2 = new Thread(new_thread);
            thread2.start();
        }
 
        public static void main(String[] args) {
            SynchronizedThread st = new SynchronizedThread();
            st.useThread();
        }
 
    }
  • 3.使用特殊域變量(volatile)實現線程同步

    a.volatile關鍵字爲域變量的訪問提供了一種免鎖機制,
    b.使用volatile修飾域至關於告訴虛擬機該域可能會被其餘線程更新,
    c.所以每次使用該域就要從新計算,而不是使用寄存器中的值
    d.volatile不會提供任何原子操做,它也不能用來修飾final類型的變量

例如:

在上面的例子當中,只需在account前面加上volatile修飾,便可實現線程同步。
//只給出要修改的代碼,其他代碼與上同
        class Bank {
            //須要同步的變量加上volatile
            private volatile int account = 100;
 
            public int getAccount() {
                return account;
            }
            //這裏再也不須要synchronized 
            public void save(int money) {
                account += money;
            }
        }

注:多線程中的非同步問題主要出如今對域的讀寫上,若是讓域自身避免這個問題,則就不須要修改操做該域的方法。用final域,有鎖保護的域和volatile域能夠避免非同步的問題。

  • 4.使用重入鎖實現線程同步

JavaSE5.0中新增了一個java.util.concurrent包來支持同步。
ReentrantLock類是可重入、互斥、實現了Lock接口的鎖, 它與使用synchronized方法和快具備相同的基本行爲和語義,而且擴展了其能力

ReenreantLock類的經常使用方法有:

ReentrantLock() :

建立一個ReentrantLock實例

lock() :

得到鎖

unlock() :

釋放鎖
注:ReentrantLock()還有一個能夠建立公平鎖的構造方法,但因爲能大幅度下降程序運行效率,不推薦使用

例如:

在上面例子的基礎上,改寫後的代碼爲:
//只給出要修改的代碼,其他代碼與上同
        class Bank {
            
            private int account = 100;
            //須要聲明這個鎖
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //這裏再也不須要synchronized 
            public void save(int money) {
                lock.lock();
                try{
                    account += money;
                }finally{
                    lock.unlock();
                }
                
            }
        }

注:關於Lock對象和synchronized關鍵字的選擇:

  • a.最好兩個都不用,使用一種java.util.concurrent包提供的機制,

    可以幫助用戶處理全部與鎖相關的代碼。
  • b.若是synchronized關鍵字能知足用戶的需求,就用synchronized,由於它能簡化代碼
  • c.若是須要更高級的功能,就用ReentrantLock類,此時要注意及時釋放鎖,不然會出現死鎖,一般在finally代碼釋放鎖
  • 5.使用局部變量實現線程同步

若是使用ThreadLocal管理變量,則每個使用該變量的線程都得到該變量的副本, 副本之間相互獨立,這樣每個線程均可以隨意修改本身的變量副本,而不會對其餘線程產生影響。

ThreadLocal 類的經常使用方法

ThreadLocal() :

建立一個線程本地變量

get() :

返回此線程局部變量的當前線程副本中的值
##### initialValue() :
返回此線程局部變量的當前線程的"初始值"
##### set(T value) :
將此線程局部變量的當前線程副本中的值設置爲value
例如:

在上面例子基礎上,修改後的代碼爲:
//只改Bank類,其他代碼與上同
        public class Bank{
            //使用ThreadLocal類管理共享變量account
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                account.set(account.get()+money);
            }
            public int getAccount(){
                return account.get();
            }
        }

注:ThreadLocal與同步機制
a.ThreadLocal與同步機制都是爲了解決多線程中相同變量的訪問衝突問題。
b.前者採用以"空間換時間"的方法,後者採用以"時間換空間"的方式

7.volatile關鍵字

volatile爲實例域的同步訪問提供了免鎖機制,若是聲明一個域爲volatile,那麼編譯器和虛擬機就直到該域可能被另外一個線程併發更新

8.java內存模型

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

9.原子性 可見性 有序性

原子性:

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

可見性:

指線程之間的可見性,既一個線程修改的狀態對另外一個線程是可見的。volatile修飾能夠保證可見性,它會保證修改的值會當即被更新到主存,因此對其餘線程是可見的,普通的共享變量不能保證可見性,由於被修改後不會當即寫入主存,什麼時候被寫入主存是不肯定的,因此其餘線程去讀取的時候可能讀到的仍是舊值

有序性:

Java中的指令重排序(包括編譯器重排序和運行期重排序)能夠起到優化代碼的做用,可是在多線程中會影響到併發執行的正確性,使用volatile能夠保證有序性,禁止指令重排
volatile能夠保證可見性 有序性,可是沒法保證原子性,在某些狀況下能夠提供優於鎖的性能和伸縮性,替代sychronized關鍵字簡化代碼,可是要嚴格遵循使用條件。

10.線程池ThreadPoolExecutor

線程池的工做原理:線程池能夠減小建立和銷燬線程的次數,從而減小系統資源的消耗,當一個任務提交到線程池時

  • a. 首先判斷核心線程池中的線程是否已經滿了,若是沒滿,則建立一個核心線程執行任務,不然進入下一步
  • b. 判斷工做隊列是否已滿,沒有滿則加入工做隊列,不然執行下一步
  • c. 判斷線程數是否達到了最大值,若是不是,則建立非核心線程執行任務,不然執行飽和策略,默認拋出異常

11.線程池的種類

1.FixedThreadPool:

可重用固定線程數的線程池,只有核心線程,沒有非核心線程,核心線程不會被回收,有任務時,有空閒的核心線程就用核心線程執行,沒有則加入隊列排隊

2.SingleThreadExecutor:

單線程線程池,只有一個核心線程,沒有非核心線程,當任務到達時,若是沒有運行線程,則建立一個線程執行,若是正在運行則加入隊列等待,能夠保證全部任務在一個線程中按照順序執行,和FixedThreadPool的區別只有數量

3.CachedThreadPool:

按需建立的線程池,沒有核心線程,非核心線程有Integer.MAX_VALUE個,每次提交
任務若是有空閒線程則由空閒線程執行,沒有空閒線程則建立新的線程執行,適用於大量的須要當即處理的而且耗時較短的任務

4.ScheduledThreadPoolExecutor:

繼承自ThreadPoolExecutor,用於延時執行任務或按期執行任務,核心線程數固定,線程總數爲Integer.MAX_VALUE

12.線程同步機制與原理,舉例說明

爲何須要線程同步?當多個線程操做同一個變量的時候,存在這個變量什麼時候對另外一個線程可見的問題,也就是可見性。每個線程都持有主存中變量的一個副本,當他更新這個變量時,首先更新的是本身線程中副本的變量值,而後會將這個值更新到主存中,可是是否當即更新以及更新到主存的時機是不肯定的,這就致使當另外一個線程操做這個變量的時候,他從主存中讀取的這個變量仍是舊的值,致使兩個線程不一樣步的問題。線程同步就是爲了保證多線程操做的可見性和原子性,好比咱們用synchronized關鍵字包裹一端代碼,咱們但願這段代碼執行完成後,對另外一個線程當即可見,另外一個線程再次操做的時候獲得的是上一個線程更新以後的內容,還有就是保證這段代碼的原子性,這段代碼可能涉及到了好幾部操做,咱們但願這好幾步的操做一次完成不會被中間打斷,鎖的同步機制就能夠實現這一點。通常說的synchronized用來作多線程同步功能,其實synchronized只是提供多線程互斥,而對象的wait()notify()方法才提供線程的同步功能。JVM經過Monitor對象實現線程同步,當多個線程同時請求synchronized方法或塊時,monitor會設置幾個虛擬邏輯數據結構來管理這些多線程。新請求的線程會首先被加入到線程排隊隊列中,線程阻塞,當某個擁有鎖的線程unlock以後,則排隊隊列裏的線程競爭上崗(synchronized是不公平競爭鎖,下面還會講到)。若是運行的線程調用對象的wait()後就釋放鎖並進入wait線程集合那邊,當調用對象的notify()notifyall()後,wait線程就到排隊那邊。這是大體的邏輯。

13.arrayList與linkedList的讀寫時間複雜度

1.ArrayList:

ArrayList是一個泛型類,底層採用數組結構保存對象。數組結構的優勢是便於對集合進行快速的隨機訪問,即若是須要常常根據索引位置訪問集合中的對象,使用由ArrayList類實現的List集合的效率較好。數組結構的缺點是向指定索引位置插入對象和刪除指定索引位置對象的速度較慢,而且插入或刪除對象的索引位置越小效率越低,緣由是當向指定的索引位置插入對象時,會同時將指定索引位置及以後的全部對象相應的向後移動一位。

2.LinkedList:

LinkedList是一個泛型類,底層是一個雙向鏈表,因此它在執行插入和刪除操做時比ArrayList更加的高效,但也由於鏈表的數據結構,因此在隨機訪問方面要比ArrayList差。

ArrayList 是線性表(數組)
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)

14.爲何HashMap線程不安全(hash碰撞與擴容致使)

HashMap的底層存儲結構是一個Entry數組,每一個Entry又是一個單鏈表,一旦發生Hash衝突的的時候,HashMap採用拉鍊法解決碰撞衝突,由於hashMapput方法不是同步的,因此他的擴容方法也不是同步的,在擴容過程當中,會新生成一個新的容量的數組,而後對原數組的全部鍵值對從新進行計算和寫入新的數組,以後指向新生成的數組。當多個線程同時檢測到hashmap須要擴容的時候就會同時調用resize操做,各自生成新的數組並rehash後賦給該map底層的數組table,結果最終只有最後一個線程生成的新數組被賦給table變量,其餘線程的均會丟失。並且當某些線程已經完成賦值而其餘線程剛開始的時候,就會用已經被賦值的table做爲原始數組,這樣也會有問題。擴容的時候 可能會引起鏈表造成環狀結構

15.進程、線程的區別

1.地址空間:

同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間。

2.資源擁有:

同一進程內的線程共享本進程的資源如內存、I/O、cpu等,可是進程之間的資源是獨立的。

3.健壯性

一個進程崩潰後,在保護模式下不會對其餘進程產生影響,可是一個線程崩潰整個進程都死掉。因此多進程要比多線程健壯。

4.資源佔用

進程切換時,消耗的資源大,效率不高。因此涉及到頻繁的切換時,使用線程要好於進程。一樣若是要求同時進行而且又要共享某些變量的併發操做,只能用線程不能用進程

5.執行過程:

每一個獨立的進程程有一個程序運行的入口、順序執行序列和程序入口。可是線程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。

6.線程是處理器調度的基本單位,可是進程不是。
7.二者都可併發執行。

16.Binder的內存拷貝過程

相比其餘的IPC通訊,好比消息機制、共享內存、管道、信號量等,Binder僅需一次內存拷貝,便可讓目標進程讀取到更新數據,同共享內存同樣至關高效,其餘的IPC通訊機制大多須要2次內存拷貝。

Binder內存拷貝的原理爲:
進程ABinder客戶端,在IPC調用前,需將其用戶空間的數據拷貝到Binder驅動的內核空間,因爲進程B在打開Binder設備(/dev/binder)時,已將Binder驅動的內核空間映射(mmap)到本身的進程空間,因此進程B能夠直接看到Binder驅動內核空間的內容改動

17.傳統IPC機制的通訊原理(2次內存拷貝)

  • 1.發送方進程經過系統調用(copy_from_user)將要發送的數據存拷貝到內核緩存區中。
  • 2.接收方開闢一段內存空間,內核經過系統調用(copy_to_user)將內核緩存區中的數據拷貝到接收方的內存緩存區。
傳統IPC機制存在2個問題:
  • 1.須要進行2次數據拷貝,第1次是從發送方用戶空間拷貝到內核緩存區,第2次是從內核緩存區拷貝到接收方用戶空間。
  • 2.接收方進程不知道事先要分配多大的空間來接收數據,可能存在空間上的浪費。

18.Java內存模型(記住堆棧是內存分區,不是模型)

Java內存模型(即Java Memory Model,簡稱JMM)自己是一種抽象的概念,並不真實存在,它描述的是一組規則或規範,經過這組規範定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式。因爲JVM運行程序的實體是線程,而每一個線程建立時JVM都會爲其建立一個工做內存(有些地方稱爲棧空間),用於存儲線程私有的數據,而Java內存模型中規定全部變量都存儲在主內存,主內存是共享內存區域,全部線程均可以訪問,但線程對變量的操做(讀取賦值等)必須在工做內存中進行,首先要將變量從主內存拷貝的本身的工做內存空間,而後對變量進行操做,操做完成後再將變量寫回主內存,不能直接操做主內存中的變量,工做內存中存儲着主內存中的變量副本拷貝,前面說過,工做內存是每一個線程的私有數據區域,所以不一樣的線程間沒法訪問對方的工做內存,線程間的通訊(傳值)必須經過主內存來完成

19.類的加載過程

類加載過程主要包含加載、驗證、準備、解析、初始化、使用、卸載七個方面,下面一一闡述。

1.加載:

獲取定義此類的二進制字節流,生成這個類的java.lang.Class對象

2.驗證:

保證Class文件的字節流包含的信息符合JVM規範,不會給JVM形成危害

3.準備:

準備階段爲變量分配內存並設置類變量的初始化

4.解析:

解析過程是將常量池內的符號引用替換成直接引用

5.初始化:

不一樣於準備階段,本次初始化,是根據程序員經過程序制定的計劃去初始化類的變量和其餘資源。這些資源有static{}塊,構造函數,父類的初始化等

6.使用:

使用過程就是根據程序定義的行爲執行

7.卸載:

卸載由GC完成。

20.什麼狀況下會觸發類的初始化

  • 一、遇到new,getstatic,putstatic,invokestatic這4條指令;
  • 二、使用java.lang.reflect包的方法對類進行反射調用;
  • 三、初始化一個類的時候,若是發現其父類沒有進行過初始化,則先初始化其父類(注意!若是其父類是接口的話,則不要求初始化父類);
  • 四、當虛擬機啓動時,用戶須要指定一個要執行的主類(包含main方法的那個類),虛擬機會先初始化這個主類;
  • 五、當使用jdk1.7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則先觸發其類初始化;

21.雙親委託模式

類加載器查找class所採用的是雙親委託模式,所謂雙親委託模式就是判斷該類是否已經加載,若是沒有則不是自身去查找而是委託給父加載器進行查找,這樣依次進行遞歸,直到委託到最頂層的Bootstrap ClassLoader,若是Bootstrap ClassLoader找到了該Class,就會直接返回,若是沒找到,則繼續依次向下查找,若是還沒找到則最後交給自身去查找

22.雙親委託模式的好處

  • 1.避免重複加載,若是已經加載過一次Class,則不須要再次加載,而是直接讀取已經加載的Class
  • 2.更加安全,確保,java核心api中定義類型不會被隨意替換,好比,採用雙親委託模式可使得系統在Java虛擬機啓動時舊加載了String類,也就沒法用自定義的String類來替換系統的String類,這樣即可以防止核心`API庫被隨意篡改。

23.死鎖的產生條件,如何避免死鎖

死鎖的四個必要條件

1.互斥條件:

一個資源每次只能被一個進程使用

2.請求與保持條件:

進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其餘進程佔有,此時請求進程被阻塞,但對本身已得到的資源保持不放。

3.不可剝奪條件:

進程所得到的資源在未使用完畢以前,不能被其餘進程強行奪走,即只能 由得到該資源的進程本身來釋放(只能是主動釋放)。

4.循環等待條件:

若干進程間造成首尾相接循環等待資源的關係
這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不知足,就不會發生死鎖。

避免死鎖的方法:

系統對進程發出每個系統可以知足的資源申請進行動態檢查,並根據檢查結果決定是否分配資源,若是分配後系統可能發生死鎖,則不予分配,不然予以分配,這是一種保證系統不進入死鎖狀態的動態策略。
在資源的動態分配過程當中,用某種方法去防止系統進入不安全狀態,從而避免發生死鎖。

通常來講互斥條件是沒法破壞的,因此在預防死鎖時主要從其餘三個方面入手

(1)破壞請求和保持條件:

在系統中不容許進程在已得到某種資源的狀況下,申請其餘資源,即要想出一個辦法,阻止進程在持有資源的同時申請其它資源。
方法一:在全部進程開始運行以前,必須一次性的申請其在整個運行過程當中所需的所有資源,
方法二:要求每一個進程提出新的資源申請前,釋放它所佔有的資源

(2)破壞不可搶佔條件:

容許對資源實行搶奪。
方式一:若是佔有某些資源的一個進程進行進一步資源請求被拒絕,則該進程必須釋放它最初佔有的資源,若是有必要,可再次請求這些資源和另外的資源。
方式二:若是一個進程請求當前被另外一個進程佔有的資源,則操做系統能夠搶佔另外一個進程,要求它釋放資源,只有在任意兩個進程的優先級都不相同的條件下,該方法才能預防死鎖。

(3)破壞循環等待條件

對系統全部資源進行線性排序並賦予不一樣的序號,這樣咱們即可以規定進程在申請資源時必須按照序號遞增的順序進行資源的申請,當之後要申請時需檢查要申請的資源的編號大於當前編號時,才能進行申請。
利用銀行家算法避免死鎖:
所謂銀行家算法,是指在分配資源以前先看清楚,資源分配後是否會致使系統死鎖。若是會死鎖,則不分配,不然就分配。
按照銀行家算法的思想,當進程請求資源時,系統將按以下原則分配系統資源:

24.App啓動流程

1.App啓動時,AMS會檢查這個應用程序所須要的進程是否存在,不存在就會請求Zygote進程啓動須要的應用程序進程
2.Zygote進程接收到AMS請求並經過fock自身建立應用程序進程,這樣應用程序進程就會獲取虛擬機的實例,還會建立Binder線程池(ProcessState.startThreadPool())和消息循環(ActivityThread looper.loop)
3.而後App進程,經過Binder IPCsytem_server進程發起attachApplication請求;
4.system_server進程在收到請求後,進行一系列準備工做後,再經過Binder IPCApp進程發送scheduleLaunchActivity請求;
5.App進程的binder線程(ApplicationThread)在收到請求後,經過handler向主線程發送LAUNCH_ACTIVITY消息;
6.主線程在收到Message後,經過反射機制建立目標Activity,並回調Activity.onCreate()等方法。
7.到此,App便正式啓動,開始進入Activity生命週期,執行完onCreate/onStart/onResume方法,UI渲染結束後即可以看到App的主界面。

25.Android單線程模型

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

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

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

26.RecyclerView在不少方面能取代ListView,Google爲何沒把ListView劃上一條過期的橫線?

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

至此,本篇已結束,若有不對的地方,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!

微信關注公衆號:  程序員Android,領福利

相關文章
相關標籤/搜索