Android多線程面試總結(jié)

和你一起終身學習,這里是程序員Android
1.什么是線程
線程就是進程中運行的多個子任務,是操作系統(tǒng)調(diào)用的最小單元
2.線程的狀態(tài)
1.New:
新建狀態(tài),new出來,還沒有調(diào)用start
2.Runnable:
可運行狀態(tài),調(diào)用start進入可運行狀態(tài),可能運行也可能沒有運行,取決于操作系統(tǒng)的調(diào)度
3.Blocked:
阻塞狀態(tài),被鎖阻塞,暫時不活動,阻塞狀態(tài)是線程阻塞在進入
4.synchronized:
關鍵字修飾的方法或代碼塊(獲取鎖)時的狀態(tài)。
5.Waiting:
等待狀態(tài),不活動,不運行任何代碼,等待線程調(diào)度器調(diào)度,wait、sleep用來暫停當前線程的執(zhí)行,以毫秒為單位,任何其它線程都可以中斷當前線程的睡眠,這種情況下將拋出InterruptedException異常
6.Timed Waiting:
超時等待,在指定時間自行返回
7.Terminated:
終止狀態(tài),包括正常終止和異常終止
3.線程的創(chuàng)建
線程創(chuàng)建的常用方法
1.繼承Thread重寫run方法
2.實現(xiàn)Runnable重寫run方法
3.實現(xiàn)Callable重寫call方法
實現(xiàn)Callable重寫call方法
實現(xiàn)Callable和實現(xiàn)Runnable類似,但是功能更強大,具體表現(xiàn)在
a.可以在任務結(jié)束后提供一個返回值,
Runnable不行b.
call方法可以拋出異常,Runnable的run方法不行c.可以通過運行
Callable得到的Fulture對象監(jiān)聽目標線程調(diào)用call方法的結(jié)果,得到返回值,(fulture.get(),調(diào)用后會阻塞,直到獲取到返回值)
4.線程中斷
一般情況下,線程不執(zhí)行完任務不會退出,但是在有些場景下,我們需要手動控制線程中斷結(jié)束任務,Java中有提供線程中斷機制相關的Api,每個線程都一個狀態(tài)位用于標識當前線程對象是否是中斷狀態(tài)
public boolean isInterrupted() //判斷中斷標識位是否是true,不會改變標識位
public void interrupt() //將中斷標識位設置為true
public static boolean interrupted() //判斷當前線程是否被中斷,并且該方法調(diào)用結(jié)束的時候會清空中斷標識位
需要注意的是interrupt()方法并不會真的中斷線程,它只是將中斷標識位設置為true,具體是否要中斷由程序來判斷,如下,只要線程中斷標識位為false,也就是沒有中斷就一直執(zhí)行線程方法
new Thread(new Runnable(){
while(!Thread.currentThread().isInterrupted()){
//執(zhí)行線程方法
}
}).start();
前邊我們提到了線程的六種狀態(tài),New 、Runnable、 Blocked、 Waiting、 Timed Waiting、 Terminated,那么在這六種狀態(tài)下調(diào)用線程中斷的代碼會怎樣呢,New和Terminated狀態(tài)下,線程不會理會線程中斷的請求,既不會設置標記位,在Runnable和Blocked狀態(tài)下調(diào)用interrupt會將標志位設置位true,在Waiting和Timed Waiting狀態(tài)下會發(fā)生InterruptedException異常,針對這個異常我們?nèi)绾翁幚恚?/p>
1.在
catch語句中通過interrupt設置中斷狀態(tài),因為發(fā)生中斷異常時,中斷標志位會被復位,我們需要重新將中斷標志位設置為true,這樣外界可以通過這個狀態(tài)判斷是否需要中斷線程
try{
....
}catch(InterruptedException e){
Thread.currentThread().interrupt();
}
2.更好的做法是,不捕獲異常,直接拋出給調(diào)用者處理,這樣更靈活
5.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ù)時,有可能導致一些很奇怪的應用程序錯誤。
6.同步方法和同步代碼塊
為何要使用同步?java允許多線程并發(fā)控制,當多個線程同時操作一個可共享的資源變量時(如數(shù)據(jù)的增刪改查), 將會導致數(shù)據(jù)不準確,相互之間產(chǎn)生沖突,因此加入同步鎖以避免在該線程沒有完成操作之前,被其他線程的調(diào)用,從而保證了該變量的唯一性和準確性。
1.同步方法
即有synchronized關鍵字修飾的方法, 由于java的每個對象都有一個內(nèi)置鎖,當用此關鍵字修飾方法時,內(nèi)置鎖會保護整個方法。在調(diào)用該方法前,需要獲得內(nèi)置鎖,否則就處于阻塞狀態(tài)。
代碼如:
public synchronized void save(){
}
注:synchronized關鍵字也可以修飾靜態(tài)方法,此時如果調(diào)用該靜態(tài)方法,將會鎖住整個類
2.同步代碼塊
即有synchronized關鍵字修飾的語句塊,被該關鍵字修飾的語句塊會自動被加上內(nèi)置鎖,從而實現(xiàn)同步
代碼如:
synchronized(object){
}
注:同步是一種高開銷的操作,因此應該盡量減少同步的內(nèi)容。
通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。
/**
* 線程同步的運用
*
* @author XIEHEJUN
*
*/
public class SynchronizedThread {
class Bank {
private int account = 100;
public int getAccount() {
return account;
}
/**
* 用同步方法實現(xiàn)
*
* @param money
*/
public synchronized void save(int money) {
account += money;
}
/**
* 用同步代碼塊實現(xiàn)
*
* @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());
}
}
}
/**
* 建立線程,調(diào)用內(nèi)部類
*/
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)實現(xiàn)線程同步
a.volatile關鍵字為域變量的訪問提供了一種免鎖機制,
b.使用volatile修飾域相當于告訴虛擬機該域可能會被其他線程更新,
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變量
例如:
在上面的例子當中,只需在account前面加上volatile修飾,即可實現(xiàn)線程同步。
//只給出要修改的代碼,其余代碼與上同
class Bank {
//需要同步的變量加上volatile
private volatile int account = 100;
public int getAccount() {
return account;
}
//這里不再需要synchronized
public void save(int money) {
account += money;
}
}
注:多線程中的非同步問題主要出現(xiàn)在對域的讀寫上,如果讓域自身避免這個問題,則就不需要修改操作該域的方法。用final域,有鎖保護的域和volatile域可以避免非同步的問題。
4.使用重入鎖實現(xiàn)線程同步
在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。ReentrantLock類是可重入、互斥、實現(xiàn)了Lock接口的鎖, 它與使用synchronized方法和快具有相同的基本行為和語義,并且擴展了其能力
ReenreantLock類的常用方法有:
ReentrantLock() :
創(chuàng)建一個ReentrantLock實例
lock() :
獲得鎖
unlock() :
釋放鎖
注:ReentrantLock()還有一個可以創(chuàng)建公平鎖的構造方法,但由于能大幅度降低程序運行效率,不推薦使用
例如:
在上面例子的基礎上,改寫后的代碼為:
//只給出要修改的代碼,其余代碼與上同
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類,此時要注意及時釋放鎖,否則會出現(xiàn)死鎖,通常在finally代碼釋放鎖5.使用局部變量實現(xiàn)線程同步
如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本, 副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產(chǎn)生影響。
ThreadLocal 類的常用方法
ThreadLocal() :
創(chuàng)建一個線程本地變量
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,那么編譯器和虛擬機就直到該域可能被另一個線程并發(fā)更新
8.java內(nèi)存模型
堆內(nèi)存是被所有線程共享的運行時內(nèi)存區(qū)域,存在可見性的問題。線程之間共享變量存儲在主存中,每個線程都有一個私有的本地內(nèi)存,本地內(nèi)存存儲了該線程共享變量的副本(本地內(nèi)存是一個抽象概念,并不真實存在),兩個線程要通信的話,首先A線程把本地內(nèi)存更新過的共享變量更新到主存中,然后B線程去主存中讀取A線程更新過的共享變量,也就是說假設線程A執(zhí)行了i = 1這行代碼更新主線程變量i的值,會首先在自己的工作線程中堆變量i進行賦值,然后再寫入主存當中,而不是直接寫入主存
9.原子性 可見性 有序性
原子性:
對基本數(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關鍵字簡化代碼,但是要嚴格遵循使用條件。
10.線程池ThreadPoolExecutor
線程池的工作原理:線程池可以減少創(chuàng)建和銷毀線程的次數(shù),從而減少系統(tǒng)資源的消耗,當一個任務提交到線程池時
a. 首先判斷核心線程池中的線程是否已經(jīng)滿了,如果沒滿,則創(chuàng)建一個核心線程執(zhí)行任務,否則進入下一步
b. 判斷工作隊列是否已滿,沒有滿則加入工作隊列,否則執(zhí)行下一步
c. 判斷線程數(shù)是否達到了最大值,如果不是,則創(chuàng)建非核心線程執(zhí)行任務,否則執(zhí)行飽和策略,默認拋出異常
11.線程池的種類
1.FixedThreadPool:
可重用固定線程數(shù)的線程池,只有核心線程,沒有非核心線程,核心線程不會被回收,有任務時,有空閑的核心線程就用核心線程執(zhí)行,沒有則加入隊列排隊
2.SingleThreadExecutor:
單線程線程池,只有一個核心線程,沒有非核心線程,當任務到達時,如果沒有運行線程,則創(chuàng)建一個線程執(zhí)行,如果正在運行則加入隊列等待,可以保證所有任務在一個線程中按照順序執(zhí)行,和FixedThreadPool的區(qū)別只有數(shù)量
3.CachedThreadPool:
按需創(chuàng)建的線程池,沒有核心線程,非核心線程有Integer.MAX_VALUE個,每次提交
任務如果有空閑線程則由空閑線程執(zhí)行,沒有空閑線程則創(chuàng)建新的線程執(zhí)行,適用于大量的需要立即處理的并且耗時較短的任務
4.ScheduledThreadPoolExecutor:
繼承自ThreadPoolExecutor,用于延時執(zhí)行任務或定期執(zhí)行任務,核心線程數(shù)固定,線程總數(shù)為Integer.MAX_VALUE
12.線程同步機制與原理,舉例說明
為什么需要線程同步?當多個線程操作同一個變量的時候,存在這個變量何時對另一個線程可見的問題,也就是可見性。每一個線程都持有主存中變量的一個副本,當他更新這個變量時,首先更新的是自己線程中副本的變量值,然后會將這個值更新到主存中,但是是否立即更新以及更新到主存的時機是不確定的,這就導致當另一個線程操作這個變量的時候,他從主存中讀取的這個變量還是舊的值,導致兩個線程不同步的問題。線程同步就是為了保證多線程操作的可見性和原子性,比如我們用synchronized關鍵字包裹一端代碼,我們希望這段代碼執(zhí)行完成后,對另一個線程立即可見,另一個線程再次操作的時候得到的是上一個線程更新之后的內(nèi)容,還有就是保證這段代碼的原子性,這段代碼可能涉及到了好幾部操作,我們希望這好幾步的操作一次完成不會被中間打斷,鎖的同步機制就可以實現(xiàn)這一點。一般說的synchronized用來做多線程同步功能,其實synchronized只是提供多線程互斥,而對象的wait()和notify()方法才提供線程的同步功能。JVM通過Monitor對象實現(xiàn)線程同步,當多個線程同時請求synchronized方法或塊時,monitor會設置幾個虛擬邏輯數(shù)據(jù)結(jié)構來管理這些多線程。新請求的線程會首先被加入到線程排隊隊列中,線程阻塞,當某個擁有鎖的線程unlock之后,則排隊隊列里的線程競爭上崗(synchronized是不公平競爭鎖,下面還會講到)。如果運行的線程調(diào)用對象的wait()后就釋放鎖并進入wait線程集合那邊,當調(diào)用對象的notify()或notifyall()后,wait線程就到排隊那邊。這是大致的邏輯。
13.arrayList與linkedList的讀寫時間復雜度
1.ArrayList:
ArrayList是一個泛型類,底層采用數(shù)組結(jié)構保存對象。數(shù)組結(jié)構的優(yōu)點是便于對集合進行快速的隨機訪問,即如果需要經(jīng)常根據(jù)索引位置訪問集合中的對象,使用由ArrayList類實現(xiàn)的List集合的效率較好。數(shù)組結(jié)構的缺點是向指定索引位置插入對象和刪除指定索引位置對象的速度較慢,并且插入或刪除對象的索引位置越小效率越低,原因是當向指定的索引位置插入對象時,會同時將指定索引位置及之后的所有對象相應的向后移動一位。
2.LinkedList:
LinkedList是一個泛型類,底層是一個雙向鏈表,所以它在執(zhí)行插入和刪除操作時比ArrayList更加的高效,但也因為鏈表的數(shù)據(jù)結(jié)構,所以在隨機訪問方面要比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)
14.為什么HashMap線程不安全(hash碰撞與擴容導致)
HashMap的底層存儲結(jié)構是一個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é)構
15.進程、線程的區(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í)行。
16.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設備(/dev/binder)時,已將Binder驅(qū)動的內(nèi)核空間映射(mmap)到自己的進程空間,所以進程B可以直接看到Binder驅(qū)動內(nèi)核空間的內(nèi)容改動
17.傳統(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ù),可能存在空間上的浪費。
18.Java內(nèi)存模型(記住堆棧是內(nèi)存分區(qū),不是模型)
Java內(nèi)存模型(即Java Memory Model,簡稱JMM)本身是一種抽象的概念,并不真實存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個變量(包括實例字段,靜態(tài)字段和構成數(shù)組對象的元素)的訪問方式。由于JVM運行程序的實體是線程,而每個線程創(chuàng)建時JVM都會為其創(chuàng)建一個工作內(nèi)存(有些地方稱為棧空間),用于存儲線程私有的數(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)存來完成
19.類的加載過程
類加載過程主要包含加載、驗證、準備、解析、初始化、使用、卸載七個方面,下面一一闡述。
1.加載:
獲取定義此類的二進制字節(jié)流,生成這個類的java.lang.Class對象
2.驗證:
保證Class文件的字節(jié)流包含的信息符合JVM規(guī)范,不會給JVM造成危害
3.準備:
準備階段為變量分配內(nèi)存并設置類變量的初始化
4.解析:
解析過程是將常量池內(nèi)的符號引用替換成直接引用
5.初始化:
不同于準備階段,本次初始化,是根據(jù)程序員通過程序制定的計劃去初始化類的變量和其他資源。這些資源有static{}塊,構造函數(shù),父類的初始化等
6.使用:
使用過程就是根據(jù)程序定義的行為執(zhí)行
7.卸載:
卸載由GC完成。
20.什么情況下會觸發(fā)類的初始化
1、遇到
new,getstatic,putstatic,invokestatic這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ā)其類初始化;
21.雙親委托模式
類加載器查找class所采用的是雙親委托模式,所謂雙親委托模式就是判斷該類是否已經(jīng)加載,如果沒有則不是自身去查找而是委托給父加載器進行查找,這樣依次進行遞歸,直到委托到最頂層的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了該Class,就會直接返回,如果沒找到,則繼續(xù)依次向下查找,如果還沒找到則最后交給自身去查找
22.雙親委托模式的好處
1.避免重復加載,如果已經(jīng)加載過一次
Class,則不需要再次加載,而是直接讀取已經(jīng)加載的Class2.更加安全,確保,
java核心api中定義類型不會被隨意替換,比如,采用雙親委托模式可以使得系統(tǒng)在Java虛擬機啟動時舊加載了String類,也就無法用自定義的String類來替換系統(tǒng)的String類,這樣便可以防止核心`API庫被隨意篡改。
23.死鎖的產(chǎn)生條件,如何避免死鎖
死鎖的四個必要條件
1.互斥條件:
一個資源每次只能被一個進程使用
2.請求與保持條件:
進程已經(jīng)保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他進程占有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。
3.不可剝奪條件:
進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能 由獲得該資源的進程自己來釋放(只能是主動釋放)。
4.循環(huán)等待條件:
若干進程間形成首尾相接循環(huá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)資源:
24.App啟動流程
1.App啟動時,AMS會檢查這個應用程序所需要的進程是否存在,不存在就會請求Zygote進程啟動需要的應用程序進程
2.Zygote進程接收到AMS請求并通過fock自身創(chuàng)建應用程序進程,這樣應用程序進程就會獲取虛擬機的實例,還會創(chuàng)建Binder線程池(ProcessState.startThreadPool())和消息循環(huán)(ActivityThread looper.loop)
3.然后App進程,通過Binder IPC向sytem_server進程發(fā)起attachApplication請求;
4.system_server進程在收到請求后,進行一系列準備工作后,再通過Binder IPC向App進程發(fā)送scheduleLaunchActivity請求;
5.App進程的binder線程(ApplicationThread)在收到請求后,通過handler向主線程發(fā)送LAUNCH_ACTIVITY消息;
6.主線程在收到Message后,通過反射機制創(chuàng)建目標Activity,并回調(diào)Activity.onCreate()等方法。
7.到此,App便正式啟動,開始進入Activity生命周期,執(zhí)行完onCreate/onStart/onResume方法,UI渲染結(jié)束后便可以看到App的主界面。
25.Android單線程模型
Android單線程模型的核心原則就是:
只能在UI線程(Main Thread)中對UI進行處理。當一個程序第一次啟動時,Android會同時啟動一個對應的 主線程(Main Thread),主線程主要負責處理與UI相關的事件,如:用戶的按鍵事件,用戶接觸屏幕的事件以及屏幕繪圖事 件,并把相關的事件分發(fā)到對應的組件進行處理。所以主線程通常又被叫做UI線 程。在開發(fā)Android應用時必須遵守單線程模型的原則:?Android UI操作并不是線程安全的并且這些操作必須在UI線程中執(zhí)行。
Android的單線程模型有兩條原則:
1.不要阻塞UI線程。
2.不要在UI線程之外訪問
Android UI toolkit(主要是這兩個包中的組件:android.widget and android.view
26.RecyclerView在很多方面能取代ListView,Google為什么沒把ListView劃上一條過時的橫線?
ListView采用的是RecyclerBin的回收機制在一些輕量級的List顯示時效率更高
至此,本篇已結(jié)束。轉(zhuǎn)載網(wǎng)絡的文章,小編覺得很優(yōu)秀,歡迎點擊閱讀原文,支持原創(chuàng)作者,如有侵權,懇請聯(lián)系小編刪除。同時感謝您的閱讀,期待您的關注。
