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个等级,所以某些
·类会被合并到同一个等级中。拥有更高等级的进程将被优先杀死。
&nbsp; &nbsp; setprop ro.FOREGROUND_APP_MEM     1536(6M)
&nbsp; &nbsp; setprop ro.VISIBLE_APP_MEM           2048(8M)
&nbsp; &nbsp; setprop ro.SECONDARY_SERVER_MEM    4096(16M)
&nbsp; &nbsp; setprop ro.HIDDEN_APP_MEM           5120(20M)
&nbsp; &nbsp; setprop ro.CONTENT_PROVIDER_MEM    5632(22M)
&nbsp; &nbsp; 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]总是最先执行完毕,其他线程执行完毕顺序大致上是优先级高的先执行完毕,但是无法保证。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页