Android的程序、執行緒與優先順序
一、結論
1、Android中程序的優先順序與垃圾回收機制相關,優先順序越低被垃圾回收的機會越大。當記憶體不足的時候,總是低優先順序的程序被最先回收;
2、Android中執行緒的優先順序與呼叫順序有關,優先順序越高被呼叫的可能性越高(注意,是可能性更高),也就是說即使執行緒A的優先順序大於執行緒B,同等情況下執行緒A不一定先於執行緒B被呼叫。
二、程序與執行緒
1、什麼是程序、執行緒
如果你想要一個程式執行得快,那麼可以將其斷開為多個片段,在單獨的處理器上執行每個片段,這是併發程式設計最主要的考慮,簡單理解:
- 程序(process):是一塊包含了某些資源的記憶體區域,是作業系統的最小可執行單元。作業系統利用程序把它的工作劃分為一些功能單元。一個程式至少對應一個程序。
- 執行緒(thread):是程序的一個實體,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位。一個程序至少對應一個執行緒。
在Android中,每個app執行時前首先建立一個程序,該程序是由Zygote fork出來的,用於承載App上執行的各種Activity/Service等元件。大多數情況一個App就執行在一個程序中,除非:
(1)配置Android:process屬性:
a.程序名為com.goodong.com.process1:
<activity android:name=".MyFirstActivity" android:process="com.goodong.com.process1" > <intent-filter> …… </intent-filter> </activity>
b.配置程序名為”:process2”(包名+process2):
<activity
android:name=".MyFirstActivity"
android:process=":process2"
>
<intent-filter>……
</intent-filter>
</activity>
注意:第2種命名程序的方式與第1中命名方式的區別在於,後者不能做到:讓不同應用程式的元件執行在同一個程序中。
(2)通過native程式碼fork程序:
應用程式的程序是由Zygote建立的,在ActivityManagerService中的startProcessLocked中呼叫了Process.start()方法。並通過連線呼叫Zygote的native方法forkAndSpecialize,執行fork任務。
2、執行緒的排程
執行緒對應用來說非常常見,比如每次new Thread().start都會建立一個新的執行緒。該執行緒與App所在程序之間資源共享,從Linux角度來說程序與執行緒除了是否共享資源外,並沒有本質的區別。
Java的執行緒機制是搶佔式的,這表示排程機制會週期性地中斷執行緒,將上下文切換到另一個執行緒,從而為每個執行緒都提供時間片,使得每個執行緒都會分配到數量合理的時間去執行它的任務。
CPU輪流為每個任務分配其佔用時間。每個任務都覺得自己在一直佔用CPU,但事實上CPU時間是劃分成片段分配給了所有任務。使用執行緒機制是一種建立透明的、可擴充套件的程式的方法,如果程式執行太慢,為機器新增一個CPU就能很容易地加快程式的執行速度。
請注意,儘管多工和多執行緒往往是使用多處理器系統的最合理方式,但多執行緒程式設計並不是僅僅針對多處理器,即使在單處理上,併發程式設計也是有用武之地的。這是因為,如果一個程式包含了多個順序執行的任務(不是併發執行),因為每個任務都可能被阻塞,一旦任務被阻塞,程式就停止執行。但在併發程式設計下,多個任務併發執行,單任務阻塞並不會直接導致程式停止執行。
三、Android程序的優先順序
1、優先順序意味著什麼
優先順序的意思是:針對多個物件的某種操作的執行順序。上面我們說過,程序是一塊記憶體區域,因為對程序而言,優先順序意味著何時釋放資源:
- 在釋放程序資源的時候,讓優先順序低的程序先釋放釋放資源;
- 如果即將被執行的程序的優先順序比正在執行的程序的優先順序高,則系統可以強行剝奪正在執行的程序的資源,讓優先順序高的程序先執行。
2、有多少個優先順序
在Android 中,程序的優先順序就是oom_adj的值,而oom_adj被定義在init.rc中:
· Define the memory thresholds at which the above process classes will
· be killed. These numbers are in pages (4k).
setprop ro.FOREGROUND_APP_ADJ 0
setprop ro.VISIBLE_APP_ADJ 1
setprop ro.SECONDARY_SERVER_ADJ 2
setprop ro.HIDDEN_APP_MIN_ADJ 7
setprop ro.CONTENT_PROVIDER_ADJ 14
setprop ro.EMPTY_APP_ADJ 15
名稱 oom_adj 解釋:
3、oom_adj值會隨著程序的狀態變化而變化
adb連線手機後,筆者從桌面上啟動了知乎,用adb shell dumpsys activity檢視activitys的分佈,可以看到activity的次序如下:
這時候另開一個命令列視窗檢視程序的程序號:
這個時候知乎有兩個程序,分別是15345和15725,使用cat /proc//oom_adj 命令檢視它們的oom_adj值:
它們的值分別是0和11(oom_adj值是可以修改);
將知乎退到後臺再查詢它們的oom_adj值:
兩個程序的值分別是9和13(退後臺前是0和11)。
4、如何根據oom_adj的值判斷回收時機
init.rc中定義垃圾回收的閾值:
· Write value must be consistent with the above properties.
· Note that the driver only supports 6 slots, so we have combined some of
· the classes into the same memory level; the associated processes of higher
· classes will still be killed first.
·寫入的值必須符合上面的屬性。注意裝置只支援6個等級,所以某些
·類會被合併到同一個等級中。擁有更高等級的程序將被優先殺死。
setprop ro.FOREGROUND_APP_MEM 1536(6M)
setprop ro.VISIBLE_APP_MEM 2048(8M)
setprop ro.SECONDARY_SERVER_MEM 4096(16M)
setprop ro.HIDDEN_APP_MEM 5120(20M)
setprop ro.CONTENT_PROVIDER_MEM 5632(22M)
setprop ro.EMPTY_APP_MEM 6144(24M)
這些數字也就是對應的記憶體閾值,一旦低於該值,Android便開始按順序關閉相應的程序 。具體的回收實現在ActivityManagerService.java中的函式trimApplications():
- 首先移除package被移走的無用程序;
基於程序當前狀態,更新oom_adj值,然後進行以下操作:
- 移除沒有activity在執行的程序。如果APP已經儲存了所有的activity狀態,結束這個APP;
- 最後,如果目前還是有很多activities 在執行,那麼移除那些activity狀態已經儲存好的activity。
當系統記憶體短缺時Android的Low Memory Killer根據需要殺死程序釋放其記憶體,簡單說就是尋找一個最合適的程序殺死,從而釋放它佔用的記憶體,最合適指的是:
- oom_adj越大
- 佔用實體記憶體越多
四、Android執行緒的優先順序
1、執行緒的簡單示範
從CPU的角度看,Android執行緒就是Java執行緒。因而Android執行緒的優先順序就是Java執行緒的優先順序(但在Android開發中,筆者似乎從未操心過執行緒的優先順序)。
Java中使用執行緒最常用的方法是:
- 實現Runnable介面,覆寫run()方法;
繼承Thread類,覆寫run()方法(實際也是實現Runnable介面)。
先實現實現Runnable介面:public class RunabbleImp implements Runnable{ @Override public void run() { doSomeThing(); } private void doSomeThing() { System.out.println(Thread.currentThread().getName()+" is running!"); } }
測試一下:
public class ThreadTest {
public static void main(String[] args) {
RunabbleImp runabbleImp = new RunabbleImp();
runabbleImp.run();
Thread thread = new Thread(new RunabbleImp());
thread.start();
}
}
列印結果:
main is running!
Thread-0 is running!
可以看出runabbleImp.run();實際上是在主執行緒中執行,而thread.start();已經開啟了一個新執行緒。
2、執行緒的優先順序
執行緒的優先順序將執行緒的重要性傳遞給排程器。儘管CPU處理現有執行緒集的順序是不確定的,但是排程器將傾向於讓優先順序高的執行緒先執行。然而,這並不意味著優先權低的執行緒將得不到執行(也就是說,優先順序不會導致死鎖)。優先順序較低的執行緒僅僅是執行的頻率較低。
可以用getPriority()獲取當前執行緒的優先順序,並且在任何時刻都可以通過setPriority()來修改它:
public class PriorityThread implements Runnable {
private int timeCount = 5;
private int priority;
public PriorityThread(int priorityIn) {
priority = priorityIn;
}
@Override
public String toString() {
return Thread.currentThread() +":"+timeCount;
}
@Override
public void run() {
Thread.currentThread().setPriority(priority);
while(true){
for (int i = 0; i < 100000; i++) {
double d = (Math.PI + Math.E)/(double)i;
if(i % 1000 == 0){
Thread.yield();
}
}
System.out.println(this);
if(--timeCount ==0){
break;
}
}
}
public class ThreadTest {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
exec.execute(new PriorityThread(Thread.MIN_PRIORITY + i));
}
}
exec.execute(new PriorityThread(Thread.MAX_PRIORITY));
exec.shutdown();
}
部分列印結果如下:
……
Thread[pool-1-thread-6,10,main]:1
……
Thread[pool-1-thread-5,5,main]:1
……
Thread[pool-1-thread-3,3,main]:1
Thread[pool-1-thread-1,1,main]:1
……
Thread[pool-1-thread-2,2,main]:1
Thread[pool-1-thread-4,4,main]:1
筆者執行多次後結果顯示Thread[pool-1-thread-6,10,main]總是最先執行完畢,其他執行緒執行完畢順序大致上是優先順序高的先執行完畢,但是無法保證。