<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在實際專案開發中會頻繁的用到執行緒,執行緒使用起來是很簡單,但是濫用執行緒會帶來效能問題, 比如啟動一個執行緒至少 佔用16kb的記憶體、執行緒過多會導致cpu的頻繁切換而cpu切換成本是很高的、消耗大量使用者電量等問題, 所以應該讓app的執行緒數保持在合理水平,這是效能優化中很重要的一部分。本文對執行緒優化方面的知識做了一個全面總結,主要內容如下:
在任意時刻,CPU 只能執行一條機器指令,每個執行緒只有獲得了 CPU 的使用權之後才能執行指令,也就是說 在任意時刻,只有一個執行緒佔用 CPU,處於執行狀態。而我們平常所說的 多執行緒並行執行,實際上說的是多個執行緒輪流獲取 CPU 的使用權,然後分別執行各自的任務。其實在可執行池當中有多個處於就緒狀態的執行緒在等待 CPU,而 JVM 負責執行緒排程,按照特定機制為多個執行緒分配 CPU 使用權。
上面的描述提到了三個主要資訊:
執行緒排程模型可以分為兩類,分別是 分時排程模型 和 搶佔式排程模型。
Android 的執行緒排程從兩個因素決定,一個是 nice
值(即執行緒優先順序),一個是 cgroup
(即執行緒排程策略)。
對於 nice 值來說,它首先是在 Process
中定義的,值越小,程序優先順序越高,預設值是 THREAD_PRIORITY_DEFAULT = 0
,主執行緒的優先順序也是這個值。修改 nice 值只需要在對應的執行緒下設定即可:
public class MyRunnable implements Runnable {<!-- --> @Override public void run() {<!-- --> Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT) } } // 附上 setThreadPriority() 檔案說明 /** * Set the priority of the calling thread, based on Linux priorities. See * {@link #setThreadPriority(int, int)} for more information. * * @param priority A Linux priority level, from -20 for highest scheduling * priority to 19 for lowest scheduling priority. * * @throws IllegalArgumentException Throws IllegalArgumentException if * <var>tid</var> does not exist. * @throws SecurityException Throws SecurityException if your process does * not have permission to modify the given thread, or to use the given * priority. * * @see #setThreadPriority(int, int) */ public static final native void setThreadPriority(int priority) throws IllegalArgumentException, SecurityException;
nice 值它還有其他的優先順序可選:
public class Process { /** * Standard priority of application threads. * Use with {@link #setThreadPriority(int)} and * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal * {@link java.lang.Thread} class. */ public static final int THREAD_PRIORITY_DEFAULT = 0; /** * Lowest available thread priority. Only for those who really, really * don't want to run if anything else is happening. * Use with {@link #setThreadPriority(int)} and * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal * {@link java.lang.Thread} class. */ public static final int THREAD_PRIORITY_LOWEST = 19; /** * Standard priority background threads. This gives your thread a slightly * lower than normal priority, so that it will have less chance of impacting * the responsiveness of the user interface. * Use with {@link #setThreadPriority(int)} and * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal * {@link java.lang.Thread} class. */ public static final int THREAD_PRIORITY_BACKGROUND = 10; /** * Standard priority of threads that are currently running a user interface * that the user is interacting with. Applications can not normally * change to this priority; the system will automatically adjust your * application threads as the user moves through the UI. * Use with {@link #setThreadPriority(int)} and * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal * {@link java.lang.Thread} class. */ public static final int THREAD_PRIORITY_FOREGROUND = -2; /** * Standard priority of system display threads, involved in updating * the user interface. Applications can not * normally change to this priority. * Use with {@link #setThreadPriority(int)} and * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal * {@link java.lang.Thread} class. */ public static final int THREAD_PRIORITY_DISPLAY = -4; /** * Standard priority of the most important display threads, for compositing * the screen and retrieving input events. Applications can not normally * change to this priority. * Use with {@link #setThreadPriority(int)} and * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal * {@link java.lang.Thread} class. */ public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; /** * Standard priority of video threads. Applications can not normally * change to this priority. * Use with {@link #setThreadPriority(int)} and * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal * {@link java.lang.Thread} class. */ public static final int THREAD_PRIORITY_VIDEO = -10; /** * Standard priority of audio threads. Applications can not normally * change to this priority. * Use with {@link #setThreadPriority(int)} and * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal * {@link java.lang.Thread} class. */ public static final int THREAD_PRIORITY_AUDIO = -16; /** * Standard priority of the most important audio threads. * Applications can not normally change to this priority. * Use with {@link #setThreadPriority(int)} and * {@link #setThreadPriority(int, int)}, <b>not</b> with the normal * {@link java.lang.Thread} class. */ public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; /** * Minimum increment to make a priority more favorable. */ public static final int THREAD_PRIORITY_MORE_FAVORABLE = -1; /** * Minimum increment to make a priority less favorable. */ public static final int THREAD_PRIORITY_LESS_FAVORABLE = +1; }
在實踐過程當中,如果只有 nice 值是不足夠的。比如有一個 app 它有1個前臺執行緒,而且它還有10個後臺執行緒,雖然後臺執行緒的優先順序比較低,但是數量比較多,這10個後臺執行緒對 CPU 的消耗量是可以影響到前臺執行緒的效能的。所以 Android 需要一種機制來處理這種情況,也就是 cgroup。
Android 借鑑了 Linux 的 cgroup 來執行 更嚴格的前臺和後臺排程策略,後臺優先順序的執行緒會被隱式的移動到後臺 group,而其他 group 的執行緒如果處於工作狀態,那麼後臺這些執行緒它們將會被限制,只有很小的機率能夠利用 CPU。
這種分離的排程策略既允許了後臺執行緒來執行一些任務,同時又不會對使用者可見的前臺執行緒造成很大的影響,讓前臺執行緒有更多的 CPU。
或許你會有疑問:哪些執行緒會被移到後臺 group?
使用 Thread
建立執行緒是最簡單、常見的非同步方式,但在實際專案中,它也就只有這個優點了,並不推薦直接使用 Thread 建立執行緒,主要有以下幾點原因:
是 Android 提供的一個自帶訊息迴圈的執行緒,它內部使用 序列的方式執行任務,比較 適合長時間執行,不斷從佇列中獲取任務的場景。
繼承了 Android Service
元件,內部建立了 HandlerThread,相比 Service 是在主執行緒執行,IntentService 是 在子執行緒非同步執行不佔用主執行緒,而且 優先順序比較高,不易被系統 kill。
AsyncTask
是 Android 提供的工具類,內部的實現是使用了執行緒池,它比較大的好處是無需自己處理執行緒切換,但需要注意 AsyncTask 不同版本執行方式不一致的問題。
java 提供了執行緒池,在實際專案中比較推薦使用執行緒池的方式實現非同步任務,它主要有以下優點:
Executors
工具類可以很方便的建立一個執行緒池,也可以自己客製化執行緒池RxJava
由強大的 Scheduler
集合提供,內部實際也是使用的執行緒池,它封裝的非常完善,可以根據任務型別的不同指定使用不同的執行緒池,比如 IO 密集型的任務可以指定 Schedulers.IO
,CPU 密集型任務可以指定 Schedulers.Computation
。
Single.just(xxx) .subscribeOn(Schedulers.IO) // 指定工作執行緒型別為 IO 密集型 .observeOn(AndroidSchedulers.mainThread()) // 指定下游接收所線上程 .subscribe();
接下來針對執行緒池的使用來做一個簡單的實踐,還是開啟我們之前的專案,這裡說一下每次實踐的程式碼都是基於第一篇啟動優化的那個案例上寫的。
首先新建一個包async,然後在包中建立一個類ThreadPoolUtils,這裡我們建立可重用且固定執行緒數的執行緒池,核心數為5,並且對外暴露一個get方法,然後我們可以在任何地方都能獲取到這個全域性的執行緒池:
public class ThreadPoolUtils { //建立定長執行緒池,核心數為5 private static ExecutorService mService = Executors.newFixedThreadPool(5, new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread(runnable,"ThreadPoolUtils");//設定執行緒名 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //設定執行緒優先順序 return thread; } }); //獲取全域性的執行緒池 public static ExecutorService getService(){ return mService; } }
然後使用的時候就可以在你需要的地方直接呼叫了,並且你在使用的時候還可以修改執行緒的優先順序以及執行緒名稱:
//使用全域性統一的執行緒池 ThreadPoolUtils.getService().execute(new Runnable() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); //修改執行緒優先順序 String oldName = Thread.currentThread().getName(); Thread.currentThread().setName("Jarchie"); //修改執行緒名稱 Log.i("MainActivity",""); Thread.currentThread().setName(oldName); //將原有名稱改回去 } });
當你的專案做的越來越大的時候一般情況下執行緒都會變的非常多,最好是能夠對整體的執行緒數進行收斂,那麼問題來了,如何知道某個執行緒是在哪裡建立的呢?不僅僅是你自己的專案原始碼,你依賴的第三方庫、aar中都有執行緒的建立,如果單靠人眼review程式碼的方式,工作量很大而且你還不一定能找的全。
並且你這次優化完了執行緒數,你還要考慮其他人新加的執行緒是否合理,所以就需要能夠建立一套很好的監控預防手段。然後針對這些情況來做一個解決方案的總結分析,主要思想就是以下兩點:
解決方案:
Hook
手段可以在建構函式中加上自己的邏輯,獲取當前的呼叫棧資訊,拿到呼叫棧資訊之後,就可以分析看出某個執行緒是否使用的是統一的執行緒池,也可以知道某個執行緒具體屬於哪個業務方。
Epic簡介
Epic使用
程式碼中使用
@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); //Hook Thread類別建構函式,兩個引數:需要Hook的類,MethodHook的回撥 DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() { //afterHookedMethod是Hook此方法之後給我們的回撥 @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param); //Hook完成之後會回撥到這裡 //實現自己的邏輯,param.thisObject可以拿到執行緒物件 Thread thread = (Thread) param.thisObject; //Log.getStackTraceString列印當前的呼叫棧資訊 Log.i(thread.getName() + "stack", Log.getStackTraceString(new Throwable())); } }); }
如果你的手機支援的話,這個時候執行程式應該就可以看到執行緒列印出來的堆疊資訊了
舉個栗子:
比如這裡有一個紀錄檔工具類,我們將它作為應用的紀錄檔基礎庫,假設它內部有一些非同步操作,原始的情況下是它自己內部實現的,然後現在在它內部對外暴露一個API,如果外部注入了一個ExecutorService,那麼我們就使用外部注入的這個,如果外部沒有注入,那就使用它預設的,程式碼如下所示:
public class LogUtils { private static ExecutorService mExecutorService; public static void setExecutor(ExecutorService executorService){ mExecutorService = executorService; } public static final String TAG = "Jarchie"; public static void i(String msg){ if(Utils.isMainProcess(BaseApp.getApplication())){ Log.i(TAG,msg); } // 非同步操作 if(mExecutorService != null){ mExecutorService.execute(() -> { ... }); }else { //使用原有的 ... } } }
統一執行緒庫
舉個栗子:根據上面的說明,可以做如下的設定:
//獲取CPU的核心數 private int CPUCOUNT = Runtime.getRuntime().availableProcessors(); //cpu執行緒池,核心數大小需要和cpu核心數相關聯,這裡簡單的將它們保持一致了 private ThreadPoolExecutor cpuExecutor = new ThreadPoolExecutor(CPUCOUNT, CPUCOUNT, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), sThreadFactory); //IO執行緒池,核心數64,這個數量可以針對自身專案再確定 private ThreadPoolExecutor iOExecutor = new ThreadPoolExecutor(64, 64, 30, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), sThreadFactory); //這裡面使用了一個count作為標記 private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); public Thread newThread(Runnable runnable) { return new Thread(runnable, "ThreadPoolUtils #" + mCount.getAndIncrement()); } };
然後在你實際專案中需要區分具體的任務型別,針對性的選擇相應的執行緒池進行使用。 以上就是對於Android執行緒優化方面的總結了,今天的內容還好不算多,覺得有用的朋友可以看看。
以上就是Android 執行緒優化知識點學習的詳細內容,更多關於Android 執行緒優化的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45