Thread
執行緒狀態:新建(new),就緒(start),運行(run),阻塞,死亡
start 方法內部呼叫了 run 方法,start 會開啟執行緒,run 只是內部方法;
sleep 會占用鎖,休眠時間到重新運行,wait 會釋放鎖;
stop 停止執行緒比較暴力,對鎖的物件進行強制解鎖,執行緒資源因此得不到正常釋放;
interrupt 不會立馬停止執行緒,只能中斷阻塞狀態的執行緒,可以捕獲到一個例外來處理,加上標識判斷是否中斷;
join 等待該執行緒完成后,才能繼續用下運行;
yield 執行緒讓步,讓自己或者其他執行緒運行,并不能保證其它執行緒就一定能獲得執行權;
wait 進入阻塞狀態,釋放鎖,需要在synchronized使用(獲取鎖后);
notify 喚起執行緒(隨機),notifyAll喚起所有執行緒,釋放鎖,需要在synchronized使用(獲取鎖后),呼叫notify和wait的必須是作用同一個物件;
創建方式
//第一種 new Thread().start(); //第二種 new Thread(Runnable實作類).start();
ThreadLocal
執行緒區域變數,為執行緒提供變數副本,每個執行緒改變副本后不會對其它執行緒造成影響,
內部通過 ThreadLocalMap 來存盤值,每個Thread類里面會有一個 ThreadLocalMap 內部變數,可以直接使用,而 ThreadLocalMap 內部使用陣列來存盤,
public class ThreadLocal<T> { static class ThreadLocalMap { //The table, resized as necessary. table.length MUST always be a power of two. private Enrty[] table; static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; //key為ThreadLocal當前物件,value就是我們存入的值 Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } } public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; }
存盤
想要存入的資料實際上并沒有存到 ThreadLocal 中去,而是以這個 ThreadLocal 實體作為key存到了當前執行緒中的一個Map(沒有鏈表結構)中去了;
如果發生hash沖突,會線性向后查找,一直找到
Entry為null的時候添加,key相同則直接更新值;程序碰到回收的過期資料,會進行探測清理操作(replaceStaleEntry()),執行方法遍歷陣列,直到碰到null停止探測,然后將過期資料清空,清空后把后面的資料往前移,提高后面查詢效率,
查詢
線性查詢,判斷hash值,如果存在相同hash值(發生過hash碰撞),則判斷是否為相同key,如果不是繼續往后迭代查找,如果一致直接回傳value;
查詢也會觸發探測清理操作,
GC回收
ThreadLocalMap 內部使用 Entry extends ThreadLocal 弱參考的陣列來存盤,但是value依然會導致殘留,根本解決方案有兩種
1.需要remove這個Entry;
2.停止當前執行緒(map也會跟著gc);
第二種方案不太好控制,最好使用第一種,
使用弱參考的原因
ThreadLocalMap 自己有探測清理操作,所以只要當前執行緒在運行,呼叫查詢增刪方法也會把value洗掉,避免記憶體泄漏OOM
Handler- 執行緒通訊工具
Handler
Handler 通過 Looper 的 prepare 方法創建Looper物件,存放在 ThreadLocal 中,保證每個執行緒的Looper是獨立的;
Looper 會持有當前執行緒的參考,并且創建一個 MessageQueue 佇列存放訊息 Message;
佇列 MessageQueue 為單向鏈表(增刪效率高),按 Message 中的 when(處理時間)欄位排序;
Handler 負責發送和處理訊息,在發送訊息( enqueueMessage() )的時候會把自己賦值給 Message 中的 target 欄位,在把 Message放入 MessageQueue 中,
Handler 將訊息 message 發送到佇列,Looper會呼叫loop方法開啟一個死回圈,讀取佇列訊息,最后拿到 Handler 實體(target),然后通過 target 呼叫 dispatchMessage 分發到 Handler 所在執行緒中的 callback 中,
一個執行緒只有一個 Looper(包含訊息佇列),可以有多個 Handler,
public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg);//post Runnable } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
記憶體泄漏
一般發生在延遲訊息中,關閉Activity后延遲訊息還未發出,MessageQueue就會持有這個Message的參考,而Message的target又持有Handler,Handler如果是內部類,會持有Activity,從而導致Activity無法被回收,引發OOM,
Handler 是內部類,持有 Activity 參考,Activity 被 finish 后 Handler 任務又沒處理完,會導致 Activity 無法被回收,需要靜態化處理,在呼叫 removeCallbacksAndMessages 清空訊息佇列,
Looper
Android啟動時就會呼叫 prepare 方法系結 Looper 物件,事件都在 Looper 的控制下,ActivityThread的main方法主要就是做訊息回圈,如果回圈停止,應用也會停止,
public static void main(String[] args) { Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
public static void loop() { final Looper me = myLooper(); ... final MessageQueue queue = me.mQueue; ... for (;;) { Message msg = queue.next(); // 獲取Message物件, ... try { msg.target.dispatchMessage(msg); } finally { ... } ... } }
MessageQueue
單鏈表結構,時間順序(when欄位),沒有訊息時,會進入阻塞狀態,此時主執行緒會釋放CPU資源進入休眠狀態,直到下個訊息過來或者事務發生,才會喚醒主執行緒作業;
如果是帶延遲的 message,根據時間順序插入到 MessageQueue,然后入阻塞狀態,如果佇列前面有訊息,會喚醒處理,
屏障訊息
擋住普通訊息以此來保證異步訊息的優先處理,屏障訊息和普通訊息的區別在于屏障訊息沒有 tartget 欄位,同樣按時間 when 排序,擋住它后面的同步訊息的分發;
postSyncBarrier 回傳 int 型別,通過這個數值可以撤銷屏障訊息,并且是私有方法,無法喚醒佇列作業,
message
一般通過 obtain 方法創建物件,會從訊息池中獲取物件,重新賦值然后回傳,避免多次創建物件,并且 obtain 方法有同步鎖,如果池子中沒有物件則new新物件,
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
AsyncTask(Android11將移除該庫,推薦用執行緒池代替)
簡化版異步任務,本質是對 Handler+Executors 的封裝,內部維護一個長度MAX的佇列;
在任務并不會立馬將任務提交給執行緒池,而是等待上一個任務完成后在提交,達到串行的目的,任務完成后利用 Handler 發送訊息,
AsyncTask 被宣告為Activity的非靜態內部類時,會持有參考,容易造成記憶體泄漏,
private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }
執行緒池 - ExecutorService
Java提供了 Executors 工廠,方便開發者創建,所有的方法回傳的都是ThreadPoolExecutor、ScheduledThreadPoolExecutor這兩個類的實體,
newCachedThreadPool
可快取執行緒池,如果超過執行緒池長度則回收空閑執行緒,若無可回收,則新建執行緒,因為允許創建的執行緒數量為MAX,執行緒過多可能會導致OOM,
newFixedThreadPool
固定長度執行緒池,可控制最大并發數,超出的執行緒會在佇列中等待,但是 LinkedBlockingQueue 佇列長度是MAX,任務多時會存在OOM,
newScheduledThreadPool
定時執行緒池,支持定時及周期性的任務執行,但是 LinkedBlockingQueue 佇列長度是MAX,任務多時會存在OOM,
newSingleThreadExecutor
單執行緒的執行緒池,保證所有任務按照指定順序執行,但是 LinkedBlockingQueue 佇列長度是MAX,可能導致OOM,
阿里手冊推薦:一般使用自定義執行緒池 ThreadPoolExecutor 創建,可以更加清楚的知道執行緒池的運行規則,精確控制池子粒度,工廠的創建也是走的這套流程,
執行緒個數不是越多越好,執行緒多了切換背景關系時間變多,反而是負擔,
背景關系切換:執行緒呼叫CPU處理任務,但是CPU內核個數比較少,執行緒呼叫完會保存狀態切換到下一個執行緒,很消耗時間,
CPU任務:一般記憶體資料計算型任務,主要消耗 CPU 資源,可以將執行緒數設定為 CPU 核心數+1,CPU任務背景關系切換比較頻繁,留出一個內核來協調其余因為頻繁帶來暫停中斷等任務,
IO任務:網路,檔案讀取等,io讀取比較耗時,處理io時CPU是不占用的,所以可以配置多一些,一般是兩倍內核數執行緒,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/445560.html
標籤:其他
