Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

高效能執行緒

參考自歐萊禮-高效能執行緒

  • Identifiez-vous pour voir les commentaires

高效能執行緒

  1. 1. 高效能執行緒 Rick。
  2. 2. 今天會講 ! Thread Handler HandlerThread Memory leaks ThreadPool Asynctask Service IntentService Binder AIDL Messenger
  3. 3. 為什麼要使用執行緒 ?
  4. 4. 情境-早上10點排隊買早餐 A. 蛋餅*1000 奶茶*1000 B. 饅頭*1 豆漿*1 C. 油條*1 D.豆漿*1
  5. 5. 由於A買太多早餐, 導致B, C, D只能買午餐
  6. 6. 因此需要使用多執行緒(MultiThread) 在Java層級, 如果要使用一個Thread可以這樣寫。 public class MyTask implements Runnable{ public void run(){ //execute your task } }
  7. 7. public class Task1 implements Runnable{ public void run(){ //蛋餅*1000 奶茶*1000 } } public class Task2 implements Runnable{ public void run(){ //饅頭*1 豆漿*1 } } //C,D的執行緒 //... 你可以這樣 買早餐
  8. 8. 多執行緒(MultiThread) 優點 同時進行多個任務, 效率高。 缺點 程式複雜度變高 增加資源消耗(context switch、memory) 資料不一致(執行緒不安全)
  9. 9. 執行緒不安全 (Thread Unsafe) int mVar = 0; public class Task1 implements Runnable{ public void run(){ mVar++; } } public class Task2 implements Runnable{ public void run(){ mVar--; } }
  10. 10. 資料不一致 sharedVar處於競態條件(race condiction), 造成最終結果不一樣, 可能是 -1, 0 或1。 因此要建立一塊臨界區段(critical section)。
  11. 11. ❖Mutual Exclusion (互斥) ➢ 任何一個時間點,最多只允許一個Process進入它自已 的C.S. 內活動,不允許多個Process同時 進 入各自的C.S.內活動。 ❖Progress (行進) 須同時滿足下面2個要件: ➢ 1.不想進入C.S.的Process不可以阻礙其它Process進入C.S. (即: 不可參與進入C.S.之決策過程) ➢ 2.必須在有限的時間內,自那些想進入C.S.的Process之中,挑選 出一個Process進入C.S. ➢ (隱含:No Deadlock) ❖Bounded Waiting (有限等待) ➢ 自Process提出進入C.S.之申請,到它獲准進入C.S.之等待時間是有限的。 (隱含:No Starvation)
  12. 12. 鎖(Lock) synchronized關鍵字可以建立一個C.S. 使用鎖會造成其他執行緒被卡住(Blocked)或死結(deadlock), 因此要小心使用。 更多詳情請參照(我的java程式比你的快10倍)
  13. 13. 物件如何交換資料? 產生某個類別的實體,當作參數傳遞給另一個實體引用 class A{ private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } } class B{ private A a; public B(A a){ this.a = a; } }
  14. 14. 又或者可以... 透過第三方協力實體, 兩個實體可透過它存取 class A{ private C c; public A(C c){ this.c = c; } } class B{ private C c; public B(C c){ this.c = c; } }
  15. 15. Activity怎麼交換資料? 由於Activity是Framework幫我們建立的, 因此取不到實體。 Android提供了兩種方式讓開發者來交換資料 Application Intent
  16. 16. Android Thread如何傳遞訊息? Handler Asynctask Binder Service IntentService 其他還有一些邪魔歪道非官方的方法 (EventBus、Otto、AndroidBus、RxJava…等)
  17. 17. Android Thread Android一開始會建立一個Main Thread, 專門用來處理UI, 因此必須把處理非UI的任務, 丟到其他Thread去執行, 否則會造成UI Thread在循序處理時, 後面任務在等待前面任務完成才能繼續往下執行。
  18. 18. 如果UI Thread沒有及時處理會怎樣?
  19. 19. 如果需要更新畫面怎麼辦? 如果Background Thread執行完畢, 需要畫面進行更新, 則會透過Handler來通知Main Thread。
  20. 20. Handler Handler是Android特有的機制, 透過他可以跟Main Thread進行溝通。 new Thread(new Runnable() { public void run() { //這邊是背景thread在運作, 這邊可以處理比較長時間或大量的運算 ((Activity) mContext).runOnUiThread(new Runnable() { public void run() { //這邊是呼叫main thread handler幫我們處理UI部分 } }); } }).start(); view.post(new Runnable(){ public void run(){ //更新畫面 } }); new Handler(mContext.getMainLooper()).post(new Runnable(){ public void run(){ //處理少量資訊或UI } });
  21. 21. 實際上你可以自行定義Looper機制 new Thread(new Runnable() { public void run() { Log.e(TAG, "A"); Looper.prepare(); new Handler().post(new Runnable(){ @Override public void run() { Log.e(TAG, "B1"); } }); new Handler().post(new Runnable(){ @Override public void run() { Log.e(TAG, "B2"); Looper.myLooper().quit(); } }); 輸出為: ● A ● B1 ● B2 ● C ● D //接左邊 Looper.loop(); Log.e(TAG, "C"); ((Activity) mContext).runOnUiThread(new Runnable() { public void run() { Log.e(TAG, "D"); } }); } }).start();
  22. 22. HandlerThread Android實作了HandlerThread, 讓你輕鬆使用Looper機制。 HandlerThread handlerThread = new HandlerThread("HandlerThread"); handlerThread.start(); Handler mHandler = new Hanlder(handlerThread.getLooper()){ public void handleMessage(Message msg){ super.handleMessage(msg); switch(msg.what){ case 1: Logger.e("message receive"); break; } } } handler.sendEmptyMessage(1); //或者 new Hanlder(handlerThread.getLooper()) .post(new Runnable(){ public void run(){ //長時間任務1 //長時間任務2 } });
  23. 23. HandlerThread優缺點 優點 HandlerThread範例 執行緒安全(循序執行) 實作簡單 程式碼乾淨 缺點 Task會blocking, Single Thread無法並行, 效率差。
  24. 24. 記憶體洩漏(Memory leaks) Dalvik VM是記憶體管理系統, 經常使用垃圾回收機制(Garbage Collection), 當一個物件不再使用, GC則會從Heap內將此物件移除回收, 如果該物件有被參照, 則將不會進行回收。 Thread如果沒寫好, 就很容易造成Memory leak。 可以透過LeakCanary這套工具來查看Memory leak。
  25. 25. 內部類別(Inner class)造成的Memory leak public class Outer{ private OtherClass other; //... public void sample(){ SampleThread sampleThread = new SampleThread(); sampleThread.start(); } private class SampleThread extends Thread{ public void run(){ Object sampleObject = new Object(); //long task } } }
  26. 26. 靜態內部類別(Static inner class) public class Outer{ private OtherClass other; //... public void sample(){ SampleThread sampleThread = new SampleThread(); sampleThread.start(); } private static class SampleThread extends Thread{ public void run(){ Object sampleObject = new Object(); //long task } } }
  27. 27. 將Runnable外包 可惜的是, 大多數程式設計師, 會將Runnable外包。 即便Thread是static, Runnable物件仍會參照到Outer物件, 造成memory leak。 public class Outer{ private Object o; public Outer(){ SampleThread sampleThread = new SampleThread(new Runnable(){ public void run(){ o = new Object(); while(true); } }); sampleThread.start(); } private static class SampleThread extends Thread{ public SampleThread(Runnable runnable){ super(runnable); } }
  28. 28. 事情沒有想像中簡單 在Android更多時候我們會這樣寫。 public class Outer{ Handler mHanlder = new Handler(){ public void handleMessage(Message msg){ //send message } }; public void doPost(){ mHanlder.post(new Runnable(){ public void run(){ //do long task } }); } } 如果有參照到Outer內的物件, 只要Thread還在執行, Outer物件就會memory leak。
  29. 29. 怎麼辦才好呢? 使用弱參考(Weak Reference)。 public class Outer{ private Object o; private MyHandler mHandler; public Outer(){ o = new Object(); mHandler = new MyHandler(this); mHandler.post(); } public Object getObj() { return o; } 如此一來, Outer物件一旦沒有參考, 則將被 視為可回收。 private static class MyHandler extends Handler { private final WeakReference<Outer> mOuter; public MyHandler(Outer outer){ mOuter = new WeakReference<Outer>(outer); } public void post(){ post(new Runnable() { public void run() { Outer out = (Outer) mOuter.get(); Object o = out.getObj(); while(true); } }); } } }
  30. 30. 減少memory leak方法 當Thread不再被使用, 請立刻終止它。 清除Message Queue Handler提供了一些方法可以清除message和callback removeCallback(Runnable r); removeMessage(int what); ...更多請參考 http://developer.android.com/reference/android/os/Handler.html
  31. 31. ThreadPool Java提供了ThreadPoolExecutor讓我們使用, 有幾個優點: Thread能保持存活, 等待新任務, 不會隨著任務建立再銷毀。 Thread Pool限制最大Thread數量, 避免系統浪費。 Thread的生命週期被Thread Pool控制。
  32. 32. ThreadPoolExecutor自行定義 ThreadPoolExecutor executor = new ThreadPoolExecutor( int corePoolSize, int maxPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue );
  33. 33. 參數說明 core pool size(核心緩衝池數量): Thread數量不會低於這個數字。 maxumum pool size(最大緩衝池數量): Thread pool的Thread最大數量 可根據底層硬體來決定數量。 int N = Runtime.getRuntime().availableProcessors(); keep-alive time(最大閒置時間): 超過閒置時間, 系統會回收core Thread數量以上的 Thread。 task queue type(任務佇列類型): 根據策略不同, 所用的演算法也會不同。
  34. 34. 內建Executor 固定尺寸執行器 : 固定大小。 Executors.newFixedThreadPool(2); 動態尺寸執行器 : 動態調整, 超過60秒沒做事就被刪掉。 Executors.newCachedThreadPool(); 單一執行緒執行器 : 就一個Thread, 後來的任務要排隊。 Executors.newSingleThreadExecutor();
  35. 35. Callable搭配Future ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Object> future = executor.submit(new Callable<Object>(){ public Object call() throws Exception{ Object obj = doLongTask(); return obj; } }); Object result = future.get(); 與Runnable不同的是Callable可以回傳結果, 透過blocking直到long task完成, 回傳物件。
  36. 36. InvokeAll與InvokeAny的操作 Executor提供同時多個Thread並行的操作。 InvokeAll : 同時並行多個Thread, 並且透過blocking來取回每一個Task的結果。 InvokeAny: 同時並行多個Thread, 只要有一個回傳成功, 則終止剩下的Task。 範例
  37. 37. ExecutorCompletionService的使用 ExecutorCompletionService可以包裹Callable的Task, 當任務結束以後, 可單一回傳結果, 直到所有任務都結束, 則進行關閉。 範例
  38. 38. AsyncTask 想執行長時間運行的任務, 最後再跟UI Thread進行溝通, Android將這些特性全部封裝在AsyncTask。
  39. 39. 如何使用Asynctask ? class MyTask extends AsyncTask<Params, Progress, Result>{ protected void onPreExecute(){ // in main thread } protected Result doInBackground(Params... params){ // in background thread } protected void onProgressUpdate(Progress... progress){ // in main thread } //接右邊 protected void onPostExecute(Result result){ // in main thread } protected void onCancelled(Result result){ // in main thread } } 範例-如何使用AsyncTask-實作
  40. 40. 參數解說 Params 輸入背景任務的資料。 Progress 由Background Thread跟UI Thread報告進度。 如果要跟UI Thread報告進度, 則可呼叫publishProgress方法。 Result 由Background Thread跟UI Thread報告結果。
  41. 41. AsyncTask的狀態 PENDING : 實體化但未執行execute RUNNING : execute已經呼叫。 FINISHED : onPostExecute或onCancelled已經被呼叫。 以上三個狀態都是不可逆的, 一旦進入了RUNNING狀態, 就無法再啟動新的執行, 必須重新產生新的實體。 可透過AsyncTask.getStatus()取得狀態資訊。
  42. 42. 使用注意事項 AsyncTask跟Thread行為一樣, 執行過後就不能再用了。 而onPostExecute跟onCancelled只會執行其中一個。 必須在UI Thread上執行AsyncTask, 否則Callback將沒作用。 內部有一個全域的ThreadPool, 一旦任務超過核心數, 則將會排入Queue內, 造成後面 任務blocking。 AsyncTask有兩種執行方式: 循序執行(SERIAL_EXECUTOR) 以及 同步進行(THREAD_POOL_EXECUTOR)
  43. 43. AsyncTask執行順序 根據版本不同, 執行順序也不一樣。 API Level execute executeOnExecutor 1 - 3 循序 無 4 - 10 並行 無 11 - 12 並行 循序/並行(可自訂) 13 + 循序 循序/並行(可自訂)
  44. 44. AsyncTask自訂Executor AsyncTask允許你自訂執行器, 可參考前面所講的內建執行器 //循序 executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, Object... objs); //並行 executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Object... objs); //自訂 executeOnExecutor(CustomExecutor, Object... objs); 如何使用AsyncTask-自訂執行器
  45. 45. AsyncTask使用情境 以下兩種狀況不適合使用AsyncTask 不使用任何參數執行任務(AsyncTask<Void, Void, Void> 沒有使用參數, 背景執行緒無法跟UI執行緒溝通, 沒有任何回傳結果。 只實做doInBackground 沒有提供報告進度或結果, 就只是個背景任務。 如果有以上的情況, 請洽Thread或HandlerThread, 謝謝。
  46. 46. AsyncTask特殊情況 AsyncTask內塞入有Looper的Thread, 也無法傳遞訊息給該執行緒。 如有上述情況, 要注意不能任意更改該Thread的Looper, 否則會很不方便阻塞其他 Thread, 如果更換Looper也會跳出RuntimeException, 真的要用Looper還是乖乖使用 HandlerThread吧! AsyncTask塞入Runnable, 這樣只是把AsyncTask當普通的Thread在使用, 優點: 如果AsyncTask內部已經存在Thread, 則會讓資源有效利用。 缺點: AsyncTask是全域環境則會干擾其他Thread運作。
  47. 47. 行程(Process)的生命週期 根據官網所提供的資訊, Android在運行多個行程, 會將行程區分成五種等級, 依據等 級來判斷該行程是否可進行回收。 Foreground Process Visible Process Service Process Background Process Empty Process
  48. 48. Foreground Process! 當Process內的Activity進入了onResume。 某個Service綁定了某個Activity且是處於onResume的狀態。 Service使用了startForeground方法 正在執行onCreate、onStart或onDestroy BroadcastReceiver正在執行onReceive
  49. 49. Visible Process ! 某Activity進入了onPause, 但是User仍看的到(Dialog)。 綁定某個Visible Activity的service。
  50. 50. Service Process! 已經使用startService或者bindService(通常是音樂撥放或者網路下載)
  51. 51. Background Process ! Activity進入onStop, 且已經不可見 系統會根據LRU演算法, 對Background Process進行回收。
  52. 52. Empty Process ! 沒有任何活動的Process。 系統優先回收的Process。
  53. 53. Service 為什麼要使用Service? 將元件的生命週期跟Thread的生命週期分開(避免前述Thread參考到元件, 在Thread 結束前無法釋放物件導致Memory leak) 當一個Process內只剩下Thread在執行, 避免Process被系統意外回收, 導致Thread被 提前結束
  54. 54. Service分成兩種形式 被啟動的Service(範例) 由第一個元件啟動請求建立, 第一個元件停止請求銷毀。 被聯繫的Service(範例) 第一個元件Bind, 該Service此時被建立, 直到所有元件Unbind才銷毀。
  55. 55. 啟動(Start)的Service public class ServiceDemo extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId); } @Override public IBinder onBind(Intent intent) { return null; } } 啟動的service非常簡單, 只要覆寫onBind並且回傳null, 接著再多覆寫 onStartCommand, 將Thread寫進此方法即可。 透過startService以及stopService控制啟動即結束, 也可以透過Service.stopSelf自行結束。
  56. 56. Service的重啟 有時候在系統記憶體吃緊的時候, 會將系統某些Service收起來, 這時候有幾個參數可 以讓系統替你重新啟動Service。 START_STICKY : Service被殺掉, 系統會重啟, 但是Intent會是null。 START_NOT_STICKY : Service被系統殺掉, 不會重啟。 START_REDELIVER_INTENT : Service被系統殺掉, 重啟且Intent會重傳。
  57. 57. 連繫(Bound)的Service(一) private LoaclServiceConnection mLoaclServiceConnection = new LoaclServiceConnection(); public class ServiceDemo extends Service { private MyBinder mBinder = new MyBinder(); private class MyBinder extends Binder{ public ServiceDemo getService(){ return ServiceDemo.this; } } public IBinder onBind(Intent intent) { return mBinder; } @Override public boolean onUnbind(Intent intent) { return super.onUnbind(intent); } }
  58. 58. 連繫(Bound)的Service(二) private class LoaclServiceConnection implements ServiceConnection{ @Override public void onServiceConnected(ComponentName name, IBinder service) { //透過Binder調用Service內的方法 } @Override public void onServiceDisconnected(ComponentName name) { //service 物件設為null } } public class MainActivity extends Activity { protected void onCreate(Bundle savedInstanceState) { bindService(new Intent(this, ServiceDemo.class), mLoaclServiceConnection, Context.BIND_AUTO_CREATE); } }
  59. 59. IntentService 如果你需要Service循序執行任務, 可以透過IntentService來完成, 背景是透過一個 HandlerThread來進行排程。 public class IntentServiceDemo extends IntentService { public IntentServiceDemo(String name) { super(name); } @Override protected void onHandleIntent(Intent intent) { //背景執行緒 } } 不需要使用的時候, 系統會自動幫你回收。 範例
  60. 60. IntentService vs. Service Service 由Client端控制 : User自行控制啟動跟結束 並行任務執行 : 啟動多個Thread 循序且可以重新安排的任務 : 任務可以被賦予優先權 e.g. 音樂服務 IntentService 循序執行
  61. 61. Binder Binder能讓應用程式在不同行程裡的執行緒之間傳遞函式與資料(方法呼叫)。 客戶端行程呼叫transact方法, 伺服端則會在onTransact接收。 交易資料由Parcel物件所組成, 根據下面網站效能比較, Parcelable比Serializable和Gson更有效率。 http://blog.prolificinteractive.com/2014/07/18/why-we-love-parcelable/ 透過下列網站可以輕鬆將物件轉換成Parcelable。 http://www.parcelabler.com/
  62. 62. Binder示意圖
  63. 63. AIDL(Android Interface Definition Language) AIDL(Android介面定義語言): 當行程想開放功能性給其他行程時, 可透過AIDL來進 行定義, 編譯後會產生支援行程間溝通(IPC, Interprocess communication)的java檔 案。
  64. 64. 同步RPC(Remote Procedure Call) 如果你在aidl內定義 interface IMyAidlInterface { String getThreadName(); } server端以實作方式實現 private IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub(){ @Override public String getThreadName() throws RemoteException { return MyActivity.class.getName(); } };
  65. 65. AIDL客戶端接收 AIDL客戶端接收 try { IMyAidlInterface mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(binder); String threadName = mIMyAidlInterface.getThreadName(); } catch (RemoteException e) { e.printStackTrace(); } 優點: 單純、好實做、執行緒安全 缺點: 用戶端會被Blocking
  66. 66. 非同步RPC 如果要實做非同步, 方法宣告為oneway且回傳值就都是void, 取而代之的是Callback。範例
  67. 67. Server端 定義AIDL import personal.givemepass.myapplication.IMyAidlInterfaceCallback; interface IMyAidlInterface { oneway void getThreadName(IMyAidlInterfaceCallback callback); } 實做Server端 private final IMyAidlInterface.Stub server = new IMyAidlInterface.Stub(){ @Override public void getThreadName(IMyAidlInterfaceCallback callback) throws RemoteException { String name = "aaa"; callback.handleMessage(name); } };
  68. 68. Client端RPC 定義AIDL interface IMyAidlInterfaceCallback { void handleMessage(String name); } 實做Client端 private IMyAidlInterfaceCallback.Stub mClient = new IMyAidlInterfaceCallback.Stub(){ @Override public void handleMessage(String name) throws RemoteException { //client 接收 } };
  69. 69. Messenger Messenger是Android提供處理IPC最簡單的方式, 實際上它底層也是以AIDL為架構, 透過Messenger會在內部使用Handler將任務排列到Thread的Message Queue執行, 是屬於執行緒安全的機制, 也可以透過Message來進行雙向溝通。 範例
  70. 70. Messenger 架構
  71. 71. Binder vs. AIDL vs. Messenger Binder主要是Android為了IPC而開發出來的。 AIDL跟Messenger最終還是會透過Binder進行IPC。 如果想要同時執行多個執行緒, 那麼AIDL是比較適合的。 如果只是想要傳遞訊息, 並且實做簡單並且執行緒安全, 則可以採用Messenger。
  72. 72. Q & A
  73. 73. Reference 我的java程式比你的快10倍 高效能執行緒 Android核心剖析 http://developer.android.com/guide/components/activities.html http://developer.android.com/guide/components/tasks-and-back-stack.html http://developer.android.com/guide/components/services.html http://developer.android.com/guide/components/processes-and-threads.html

×