一.BlockingQueue阻塞隊列
阻塞隊列,Java給出的解釋如下:
在隊列的基礎上額外支持了這些操作:當取元素的時候會等待隊列至不為空的時候;當添加元素的時候會等待隊列有可用空間的時候。
解釋完阻塞隊列了,思考一下問題:
1.為什么Java要提供這么一個隊列?即阻塞隊列的存在性是什么?
多線程環境中,通過隊列可以很容易實現數據共享,比如經典的“生產者”和“消費者”模型中,通過隊列可以很便利地實現兩者之間的數據共享。假設我們有若干生產者線程,另外又有若干個消費者線程。如果生產者線程需要把準備好的數據共享給消費者線程,利用隊列的方式來傳遞數據,就可以很方便地解決他們之間的數據共享問題。但如果生產者和消費者在某個時間段內,萬一發生數據處理速度不匹配的情況呢?理想情況下,如果生產者產出數據的速度大于消費者消費的速度,并且當生產出來的數據累積到一定程度的時候,那么生產者必須暫停等待一下(阻塞生產者線程),以便等待消費者線程把累積的數據處理完畢,反之亦然。然而,在concurrent包發布以前,在多線程環境下,我們每個程序員都必須去自己控制這些細節,尤其還要兼顧效率和線程安全,而這會給我們的程序帶來不小的復雜度。
2.阻塞隊列實現原理是什么?
在多線程領域:所謂阻塞,在某些情況下會掛起線程(即阻塞),一旦條件滿足,被掛起的線程又會自動被喚醒。
- 當隊列中沒有數據的情況下,消費者端的所有線程都會被自動阻塞(掛起),直到有數據放入隊列。
- 當隊列中填滿數據的情況下,生產者端的所有線程都會被自動阻塞(掛起),直到隊列中有空的位置,線程被自動喚醒。
3.BlockingQueue核心方法
BlockingQueue接口有以下方法:
boolean add(E e);
boolean offer(E e);
void put(E e) throws InterruptedException;
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
E take() throws InterruptedException;
E poll(long timeout, TimeUnit unit) throws InterruptedException;
int remainingCapacity();
boolean remove(Object o);
boolean contains(Object o);
int drainTo(Collection<? super E> c);
int drainTo(Collection<? super E> c, int maxElements);
放入數據
- (1)offer(anObject):表示如果可能的話,將anObject加到BlockingQueue里,即如果BlockingQueue可以容納,則返回true,否則返回false.(本方法不阻塞當前執行方法
的線程); - (2)offer(E o, long timeout, TimeUnit unit):可以設定等待的時間,如果在指定的時間內,還不能往隊列中加入BlockingQueue,則返回失敗。
- (3)put(anObject):把anObject加到BlockingQueue里,如果BlockQueue沒有空間,則調用此方法的線程被阻斷直到BlockingQueue里面有空間再繼續.
- (4)add(anObject):它與offer(anObject)功能大致一致,不過對此方法官方有如下備注:When using a capacity-restricted queue, it is generally preferable to use offer(Object),即:當使用容量限制的隊列時,通常傾向于選擇offer方法,容量限制的隊列指的是在初始化隊列是必須指定容量大小,eg:ArrayBlockingQueue
獲取數據
- (1)poll(time):取走BlockingQueue里排在首位的對象,若不能立即取出,則可以等time參數規定的時間,取不到時返回null;
- (2)poll(long timeout, TimeUnit unit):從BlockingQueue取出一個隊首的對象,如果在指定時間內,隊列一旦有數據可取,則立即返回隊列中的數據。否則知道時間超時還沒有數據可取,返回失敗。
- (3)take():取走BlockingQueue里排在首位的對象,若BlockingQueue為空,阻斷進入等待狀態直到BlockingQueue有新的數據被加入;
- (4)drainTo():一次性從BlockingQueue獲取所有可用的數據對象(還可以指定獲取數據的個數),通過該方法,可以提升獲取數據效率;不需要多次分批加鎖或釋放鎖。
4.BlockingQueue實現類
5.分析ArrayBlockingQueue內部機制
ArrayBlockingQueue官方解釋為: A bounded blocking queue backed by an array,即由數組支持的有界阻塞隊列;在構造ArrayBlockingQueue時需要指定容量,也就意味著底層數組一旦創建了,容量就不能改變了。那對此我提出一下問題。
- 如何用固定容量的數組實現隊列的FIFO機制
- 如何在入棧,出棧的時候,達到阻塞效果,詳細的說要做到當取元素的時候會等待隊列至不為空的時候;當添加元素的時候會等待隊列有可用空間的時候。
我們看下ArrayBlockingQueue是如何解決第一個問題的,ArrayBlockingQueue出入棧的源碼如下:
final Object[] items;
int takeIndex;
int putIndex;
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal();
}
private E dequeue() {
// assert lock.getHoldCount() == 1;
// assert items[takeIndex] != null;
final Object[] items = this.items;
@SuppressWarnings("unchecked")
E x = (E) items[takeIndex];
items[takeIndex] = null;
if (++takeIndex == items.length) takeIndex = 0;
count--;
if (itrs != null)
itrs.elementDequeued();
notFull.signal();
return x;
}
這里ArrayBlockingQueue內部記錄兩個全局變量,putIndex與takeIndex默認都為0,入棧的時候往putIndex對應下標的位置插入,同時putIndex自增1,當增加到固定容量-1的時候,再將putIndex置為0;當出棧的時候將takeIndex對應下標的位置清空即置為null,同時takeIndex自增1,當增加到固定容量-1的時候,再將takeIndex置為0;這樣做的好處是時間復雜度最低,無需在出入棧的時候去遍歷數組。
ArrayBlockingQueue是如何解決第二個問題的呢?即如何在入棧,出棧的時候,達到阻塞效果呢
int count;
final ReentrantLock lock;
private final Condition notEmpty;
private final Condition notFull;
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}
這里ArrayBlockingQueue首先聲明了以上幾個全局變量,并在構造函數中進行初始化。在這里用到java.util.concurrent.locks.ReentrantLock與java.util.concurrent.locks.Condition的使用,這兩個類即做到了線程安全的目的,這里先拓展講一下線程安全的知識點
5.1 線程安全探索
在講這兩個locks類之前,先看一下以下例子:
public class Demo {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(15);
for (int i = 0; i < 500; i++){
executorService.execute(() -> {
add();
});
}
Thread.sleep(1000);
System.out.println(count);
}
private static int add(){
return ++count;
}
}
該Demo代碼最終打印的不是500,而是小于500的數字,為了保證結果和預期一致,則要實現線程安全
首先提問:如何保證線程安全呢?
多線程有三個核心點:原子性、可見性、有序性;原子性即一個操作(有可能包含有多個子操作)要么全部執行(生效),要么全部都不執行(都不生效);可見性即當多個線程并發訪問共享變量時,一個線程對共享變量的修改,其它線程能夠立即看到;有序性即程序執行的順序按照代碼的先后順序執行。
只要實現以上3個特性,就可以做到線程安全
1.互斥同步(阻塞同步/悲觀的并發策略)
互斥同步(Mutual Exclusion & Synchronization)是最常見的一種并發正確性保證手段,同步是指在多個線程并發訪問共享數據時,保證共享數據在同一時刻只被一條(或者是一些,使用信號量的時候)線程使用。而互斥是實現同步的一種手段,臨界區(Critical Section)、互斥量(Mutex)和信號量(Sempahore)都是主要的互斥實現方式。因此,在這四個字里面,互斥是因,同步是果,互斥是方法,同步是目的。互斥即為阻塞,讓其他線程阻塞;其實現了操作的原子性(這個操作沒有被中斷) 和 可見性(對數據進行更改后,會立馬寫入到內存中,其他線程在使用到這個數據時,會獲取到最新的數據)
常用Synchronized 關鍵字;synchronized實現同步的基礎就是:Java中的每一個對象都可以作為鎖。具體表現為:
- 1.普通同步方法,鎖是當前實例對象 public synchronized void **()
- 2.靜態同步方法,鎖是當前類的Class對象 private synchronized static void **()
- 3.同步方法塊,鎖是Synchronized括號里匹配的對象 synchronized (lockContent){}
synchronized同步的原理是
首先Java代碼在編譯之后會變成Java字節碼,字節碼被類加載器加載到JVM里,JVM執行字節碼,最終需要轉化為匯編指令在CPU上執行,JAVA中所使用的并發機制依賴于JVM的實現和CPU的指令。
synchronized經過編譯之后,會在同步塊的前后生成 monitorenter 和monitorexit這兩個字節碼指令。這兩個字節碼指令之后有一個reference類型(存在于java虛擬機棧的局部變量表中,可以根據reference數據,來操作堆上的具體對象)的參數來指明要鎖定和解鎖的對象。根據虛擬機規范,在執行monitorenter 指令時,首先會嘗試獲取對象的鎖(堆內存中存儲的對象內容中,起對象頭帶有線程持有的鎖),如果該對象沒有被鎖定,或者當前線程已經擁有了那個對象的鎖,把鎖的計數器加一。若獲取對象失敗,那當前線程就要阻塞等待,知道對象鎖被另一個線程釋放。
鎖的可重入性的可重入是指:當一個線程獲得一個對象鎖后,再次請求該對象鎖時是可以獲得該對象的鎖的。比如
public class Test01 {
public synchronized void sm1(){
System.out.println("同步方法1");
//線程執行sm1()方法,默認this作為鎖對象,在sm1()方法中調用了sm2()方法,注意當前線程還是持有this鎖對象的
//sm2()同步方法默認的鎖對象也是this對象, 要執行sm2()必須先獲得this鎖對象,當前this對象被當前線程持有,可以 再次獲得this對象, 這就是鎖的可重入性. 假設鎖不可重入的話,可能會造成死鎖
sm2();
}
private synchronized void sm2() {
System.out.println("同步方法2");
sm3();
}
private synchronized void sm3() {
System.out.println("同步方法3");
}
public static void main(String[] args) {
Test01 obj = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
obj.sm1();
}
}).start();
}
}
線程間通信
多個線程間相互配合工作,需要依靠線程間通信。在 Object對象中 , 提供了wait/notify/notifyall,可以用于控制線程的狀態。
public class WaitNotifyDemo {
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(() -> {
synchronized (object) {
System.out.println("ThreadA---Start");
try {
// 釋放鎖等待
object.wait();
System.out.println("ThreadA---Wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread threadB = new Thread(() -> {
synchronized (object) {
System.out.println("ThreadB---Start");
// 喚醒ThreadA,需要注意的是(ThreadB需要執行完同步代碼)
object.notify();
System.out.println("ThreadB---Wait");
}
});
threadA.start();
TimeUnit.SECONDS.sleep(2);
threadB.start();
threadA.join();
threadB.join();
}
}
打印如下:
ThreadA---Start
ThreadB---Start
ThreadB---Wait
ThreadA---Wait
在這里要特別強調一點,官方對wait有如下備注:
* A thread can also wake up without being notified, interrupted, or
* timing out, a so-called <i>spurious wakeup</i>. While this will rarely
* occur in practice, applications must guard against it by testing for
* the condition that should have caused the thread to be awakened, and
* continuing to wait if the condition is not satisfied. In other words,
* waits should always occur in loops, like this one:
* <pre>
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait(timeout);
* ... // Perform action appropriate to condition
* }
java為了避免所謂的虛假喚醒,在多線程判斷中,用while來代替if。所謂的虛假喚醒指的是:當一個條件滿足時,很多線程都被喚醒了,但是只有其中部分是有用的喚醒,其它的喚醒都是無用功
這樣在同步方法條件判斷時,用while去做判斷,避免了notifyall,讓那些不滿足條件的線程依然還在掛起中
synchronized可重入鎖的實現
java 在虛擬機中除了線程計數器,java虛擬機棧 是線程私有的,其余的java堆,方法區,和運行時常量池都是線程共享的內存區域。java堆是存儲對象和數組的,但是對象在內存中的存儲布局可以分為三塊區域:對象頭,實例數據(對象真正存儲的有效信息,程序代碼中所定義的各個類型的字段內容),對齊填充。
每個鎖關聯一個線程持有者和一個計數器。當計數器為0時表示該鎖沒有被任何線程持有,那么任何線程都都可能獲得該鎖而調用相應方法。當一個線程請求成功后,JVM會記下持有鎖的線程,并將計數器計為1。此時其他線程請求該鎖,則必須等待。而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增。當線程退出一個synchronized方法/塊時,計數器會遞減,如果計數器為0則釋放該鎖。
存在的問題
互斥同步最主要的問題就是進行線程阻塞和喚醒所帶來的性能問題,因而這種同步又稱為阻塞同步,它屬于一種悲觀的并發策略,即線程獲得的是獨占鎖。獨占鎖意味著其他線程只能依靠阻塞來等待線程釋放鎖。而在 CPU 轉換線程阻塞時會引起線程上下文切換,當有很多線程競爭鎖的時候,會引起 CPU 頻繁的上下文切換導致效率很低。synchronized 采用的就是這種并發策略。
隨著指令集的發展,我們有了另一種選擇:基于沖突檢測的樂觀并發策略,通俗地講就是先進性操作,如果沒有其他線程爭用共享數據,那操作就成功了,如果共享數據被爭用,產生了沖突,那就再進行其他的補償措施(最常見的補償措施就是不斷地重拾,直到試成功為止),這種樂觀的并發策略的許多實現都不需要把線程掛起,因此這種同步被稱為非阻塞同步
2.非阻塞同步(樂觀并發策略)
因為使用synchronized的時候,只能有一個線程可以獲取對象的鎖,其他線程就會進入阻塞狀態,阻塞狀態就會引起線程的掛起和喚醒,會帶來很大的性能問題,所以就出現了非阻塞同步的實現方法。互斥同步的原理是用同步實現了原子性和可見性。而非阻塞同步的原理就是: 先進行操作,如果沒有其他線程爭用共享數據,那么操作就成功了,如果共享數據有爭用,就采取補償措施(不斷地重試)
常用類如下:
我們可以看下AtomicInteger類incrementAndGet方法
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
由以上源碼可知,自增操作,就是不斷在for循環,直到比較操作為true,才會跳出循環,這就是補償措施; 比較操作compareAndSet最終調用了unsafe的compareAndSwapInt方法,即原子操作CAS指令(Compare-And-Swap比較并交換),它有三個操作數:內存位置,舊的預期值,新值,在執行CAS操作時,當且僅當內存地址的值符合舊的預期值的時候,才會用新值來更新內存地址的值,否則就不執行更新。
3.無同步方案
線程本地存儲:將共享數據的可見范圍限制在一個線程中。這樣無需同步也能保證線程之間不出現數據爭用問題。常用的是ThreadLocal類
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
get()方法是用來獲取ThreadLocal在當前線程中保存的變量副本,set()用來設置當前線程中變量的副本,remove()用來移除當前線程中變量的副本,initialValue()是一個protected方法,一般是用來在使用時進行重寫的,它是一個延遲加載方法;
其實引起線程不安全最根本的原因 就是 :線程對于共享數據的更改會引起程序結果錯誤。線程安全的解決策略就是:保護共享數據在多線程的情況下,保持正確的取值。
所以要想解決Demo打印值符合預期值500,既可以讓count聲明為AtomicInteger類型;也可以對add靜態方法,用synchronized修改(當然也可以在調用add方法地方加synchronized修飾)
executorService.execute(() -> {
synchronized (Demo.class){
add();
}
});
或者
private static int add(){
synchronized (Demo.class){
return ++count;
}
}
5.2聊聊ReentrantLock與Condition
以上例子也可以用ReentrantLock類
public class Demo {
private static int count = 0;
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(15);
for (int i = 0; i < 500; i++){
executorService.execute(() -> {
add();
});
}
Thread.sleep(1000);
System.out.println(count);
}
private static int add(){
lock.lock();
try {
return ++count;
}finally {
lock.unlock();
}
}
}
ReetrantLock 采用的樂觀并發策略,就是非阻塞同步;在基本用法上,ReentranLock與synchronized很相似,他們都具備一樣的線程重入特性,synchronized 是 JVM 實現的,而 ReentrantLock 是 JDK 實現的,一個表現為API層面的互斥鎖(lock和unlock方法配合try/finally語句塊來完成),一個表現為原生語法層面的互斥鎖;ReentrantLock 可中斷,而 synchronized 不行。即ReentrantLock比synchronized增加了一些高級功能,主要有以下三項:等待可中斷、可實現公平鎖,一級鎖可以綁定多個條件。
- 等待可中斷是指當持有鎖的線程長期不是放鎖的時候,正在等待的線程可以選擇放棄等待,改為處理其他事情,可中斷特性對處理執行時間非常長的同步塊很有幫助。
- 公平鎖是指多個線程在等待同一鎖時,必須按照申請鎖的時間順序來依次獲得鎖;而非公平鎖則不保證這一點,在鎖被釋放的時,任何一個等待鎖的線程都有機會獲得鎖。synchronized中的鎖是非公平的,ReentrantLock默認情況下也是非公平的,但可以通過帶布爾值的構造函數要求使用公平鎖。
- 鎖綁定多個條件是指一個ReentrantLock對象可以同時綁定多個Condition對象,而在sychronized中,鎖對象的wait()和notify()或notifyAll()方法可以實現一個隱含的條件,如果要和多余一個的條件關聯的時候,就不得不額外地添加一個鎖,而ReentrantLock則無須這樣做,只需要多次調用newCondition()方法即可。
ReetrantLock 當調用Condition的await方法后,當前線程會釋放鎖并等待,而其他線程調用condition 對象的 signal 或者 signalall 方法通知被阻塞的線程,然后自己執行 unlock 釋放鎖,被喚醒的線程獲得之前的鎖繼續執行,最后釋放鎖。
所以,condition 中兩個最重要的方法,一個是 await,一個是 signal 方法await:把當前線程阻塞掛起signal:喚醒阻塞的線程
我們回來再想想如何實現阻塞隊列的代碼原理,即:當添加元素的時候會等待隊列有可用空間的時候;當取元素的時候會等待隊列至不為空的時候;
- 為了做到插入要等待有空間,在插入時判斷count==size,符合就阻塞線程,不符合就執行插入操作
- 為了做到取出要等待不為空,在取出時判斷count==0,符合就阻塞線程,不符合就執行取出操作
但是這兩個操作是彼此影響的,即插入線程和取出線程是要通信的,插入完成之后,通知取出線程繼續操作;取出完成之后,通知插入線程繼續操作;
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
這里有兩個Condition,一個是notFull,一個是notEmpty。當執行入隊列時先判count!=size條件,如果不符合會執行notFull的await操作,會去阻塞線程并釋放線程鎖,此時出隊列操作中,如果有阻塞線程的情況,收到釋放線程鎖信號會繼續判斷notEmpty條件,當count!=0時,會執行出隊列實際操作,執行完成后執行notFull的signal方法,通知入隊列的阻塞線程繼續判斷notFull條件,如何有多余空間,執行實際入隊列操作
ReentrantLock和Condition具體使用與分析可看以下文章:
ReentrantLock和condition源碼淺析(一)
Java多線程之ReentrantLock與Condition
漫談Java中的互斥同步
什么是線程安全?如何保證線程安全?
二.AsyncLayoutInflater分析
google對此解釋如下:Helper class for inflating layouts asynchronously,即異步加載布局的幫助類;傳統中,對于布局文件的加載必須要在主線程,為什么要在主線程呢?是因為ViewRootImpl在以下操作前都做了checkThread判斷
checkThread源碼如下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
public ViewRootImpl(Context context, Display display) {
-------------------
mThread = Thread.currentThread();
-------------------
}
有以上源碼可知,checkThread方法判斷,當前操作線程是否等于當時初始化ViewRootImpl時的線程。如果不相等拋出“Only the original thread that created a view hierarchy can touch its views”異常,Android View 繪制流程之 DecorView 與 ViewRootImpl
為了加載耗時布局,官方提供了AsyncLayoutInflater異步加載工具。那它內部如何實現異步加載布局的呢?我們閱讀一下源碼
public final class AsyncLayoutInflater {
LayoutInflater mInflater;
Handler mHandler;
InflateThread mInflateThread;
public AsyncLayoutInflater(@NonNull Context context) {
mInflater = new BasicInflater(context);
mHandler = new Handler(mHandlerCallback);
mInflateThread = InflateThread.getInstance();
}
@UiThread
public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
@NonNull OnInflateFinishedListener callback) {
if (callback == null) {
throw new NullPointerException("callback argument may not be null!");
}
InflateRequest request = mInflateThread.obtainRequest();
request.inflater = this;
request.resid = resid;
request.parent = parent;
request.callback = callback;
mInflateThread.enqueue(request);
}
}
AsyncLayoutInflater 構造器內初始化了主線程的Handler和單例InflateThread 線程以及一個自定義的布局加載器BasicInflater;當用戶調用inflate方法時,就會調用obtainRequest方法從InflateThread 線程內獲取一個InflateRequest加載布局請求,之后調用enqueue去入隊列到InflateThread線程的隊列中;我們再看下InflateThread 內部類的源碼:
private static class InflateThread extends Thread {
private static final InflateThread sInstance;
static {
sInstance = new InflateThread();
sInstance.start();
}
public static InflateThread getInstance() {
return sInstance;
}
private ArrayBlockingQueue<InflateRequest> mQueue = new ArrayBlockingQueue<>(10);
private SynchronizedPool<InflateRequest> mRequestPool = new SynchronizedPool<>(10);
public void runInner() {
InflateRequest request;
try {
request = mQueue.take();
} catch (InterruptedException ex) {
// Odd, just continue
Log.w(TAG, ex);
return;
}
try {
request.view = request.inflater.mInflater.inflate(
request.resid, request.parent, false);
} catch (RuntimeException ex) {
// Probably a Looper failure, retry on the UI thread
Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI"
+ " thread", ex);
}
Message.obtain(request.inflater.mHandler, 0, request)
.sendToTarget();
}
@Override
public void run() {
while (true) {
runInner();
}
}
public InflateRequest obtainRequest() {
InflateRequest obj = mRequestPool.acquire();
if (obj == null) {
obj = new InflateRequest();
}
return obj;
}
public void releaseRequest(InflateRequest obj) {
obj.callback = null;
obj.inflater = null;
obj.parent = null;
obj.resid = 0;
obj.view = null;
mRequestPool.release(obj);
}
public void enqueue(InflateRequest request) {
try {
mQueue.put(request);
} catch (InterruptedException e) {
throw new RuntimeException(
"Failed to enqueue async inflate request", e);
}
}
}
由源碼可知,只要第一次初始化InflateThread 類,就會啟動一個單例InflateThread 線程,注意run方法內部用了while循環runInner,是為了保證該線程都不會關閉;obtainRequest方法就是從mRequestPool的同步池內獲取Request加載布局請求(其實這樣的操作等同于Handler的obtainMessage方法去獲取消息,而不是直接實例化Message,這樣就可以避免內存抖動問題);enqueue方法就是入隊列,注意看mQueue是一個ArrayBlockingQueue類型的隊列,就是上文講解的數組阻塞隊列,通過調用put方法可達到其內部數組沒有空間存放時而去阻塞隊列,直到調用take出隊列操作等到數組有空間時,才會真正存入數組中;線程run方法不斷調用runInner方法,從阻塞隊列中取出request加載布局請求,從而異步執行BasicInflater的inflate方法
我們看下BasicInflater類源碼
private static class BasicInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
BasicInflater(Context context) {
super(context);
}
@Override
public LayoutInflater cloneInContext(Context newContext) {
return new BasicInflater(newContext);
}
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
}
可以看下LayoutInflater部分源碼,如下:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
inflate方法內調用的createViewFromTag方法最終回、會調用onCreateView方法;我們在看下AsyncLayoutInflater的Handler傳入的mHandlerCallback
private Callback mHandlerCallback = new Callback() {
@Override
public boolean handleMessage(Message msg) {
InflateRequest request = (InflateRequest) msg.obj;
if (request.view == null) {
request.view = mInflater.inflate(
request.resid, request.parent, false);
}
request.callback.onInflateFinished(
request.view, request.resid, request.parent);
mInflateThread.releaseRequest(request);
return true;
}
};
mHandlerCallback 的主線程回調中會去檢查異步請求后的view,如果為空,主線程會去加載布局,達到二次檢驗,避免異步加載后的View為空