该篇文章用于记录Thread的一些总结,包含Thread的scheduler、通信、Android应用的线程等内容。

一、Thread的scheduler

所谓线程的scheduler,就是线程之间竞争cpu资源的策略,在Java Thread中,有两个变量影响竞争策略,一个为线程的nice值,一个为线程的cgroup。

首先nice值,即为线程的priority值,为什么叫nice值呢,可以理解为友善度,nice值越高,代表友善度越高,代表越谦让,越容易被其他线程所抢先执行。那么如何设置该值呢,在Java中通过调用Thread.setPriority()来设置。在Android中,其封装了另一套API,可以通过调用Process.setThreadPriority()来进行设置。需要注意这两个方法的区别:首先Java的setPriority()方法,其可以设置的最小优先级,为Thread.MIN_PRIORITY为1,而其可以设置的最大优先级为Thread.MAX_PRIORITY,为10,而默认优先级则为Thread.NORM_PRIORITY,值为5。最小、最大、默认分别对应Linux中的19、-8和0;其次,这两个方法不能混用,比如我们使用setThreadPriority()设置的值,通过Android的getPriority()并不能得到。

在竞争cpu资源时,nice值越小,越有机会抢占cpu执行,那么是不是nice值高的线程,就不能永远得到cpu执行呢?这就引入了CFS策略,也就是completely fair scheduler。这种策略不但会参考单个线程的优先级,还会追踪每个线程已经获取到的cpu时间片数量(time slice),如果高优先级的线程已经执行了很长时间,但低优先级的线程一直在等待,后续系统会保证低优先级的线程也能获取更多的CPU时间。显然使用这种调度策略的话,优先级高的线程并不一定能在争取time slice上有绝对的优势,所以Android系统在线程调度上使用了cgroups的概念,cgroups能更好的凸显某些线程的重要性,使得优先级更高的线程明确的获取到更多的time slice。Android将线程分为多个group,其中两类group尤其重要。一类是default group,或者叫做foreground group,UI线程属于这一类。另一类是background group,工作线程归属到这一类。background group当中所有的线程加起来总共也只能分配到5~10%的time slice,剩下的全部分配给default group,这样设计显然能保证UI线程绘制UI的流畅性。

那么你可能要问,哪些线程属于default group,哪些线程属于background group呢?按照[1]篇博客的说法,使用Process.setThreadPriority()设置,当nice值大于等于THREAD_PRIORITY_BACKGROUND,就会从属于background group,其余情况就会从属于foreground group。那么实际情况呢?我们下面来做一个实验。这里以5.1.0环境以及处于前台的进程com.test.app来为例子,我们输入

1
adb shell ps | grep com.test.app

得到如下内容
Thread-1

这里注意第一列,其代表USER。我们可以使用这个USER来继续查找,其中 -t为显示thread,-P为显示所属cgroup。

1
adb shell ps -t -P | grep u0_a63

显示结果为
Thread-2

Thread-3

发现其所属cgroup都为fg。也就是foreground group。即使那些我们设置了nice值为THREAD_PRIORITY_BACKGROUND的线程。而当我们将应用切换到后台时,

Thread-4

其所有的线程cgroup又为bg了。所以按照[1]的说法,并不正确。Android目前的cgroup其实是按照进程来分组的。

当我们使用4.1.1的环境时,处于前台的nice值为THREAD_PRIORITY_BACKGROUND。cgroup又为bg了。

Thread-5。看来是和版本有关,这里还需要再研究。

二、Android Thread
这里需要单独强调一下Android Thread中新建的thread的priority。目前Android的线程有UI Thread、Binder Thread、Background Thread。需要注意的是,默认的UI线程,我们使用Process.getThreadPriority()时得到的nice值为-6。那么我们从ui线程中new出的Thread,其nice值为多少呢?其实由于这些new出的线程,其父线程id都是ui thread(虽然第一行显示的为PID和PPID,但是由于我们使用了-t参数,所以当该行数据代表线程时,列出的都是thread id),其nice值都和父线程一样,这样就造成了,即使我们新开了一个后台线程去处理长时间的任务,还是会造成其和UI线程抢占cpu的问题。这就需要我们在new Thread的时候再去设置nice值。

三、线程间通信
Java中线程间通信的方式,包含Pipes、Shared Memory(heap)、Signal、BlockingQueue。

1.Pipes
其本身是一个在内存中的buffer,并且只有两头的线程才可以访问,这就保证了线程安全。pipe可以传递二进制或者character data。当我们传送二进制时,使用PipedOutputStream (in the producer) 以及 PipedInputStream (in the consumer)。当我们使用character data时,使用PipedWriter (in the producer) 和 PipedReader (in the consumer)

这里以PipedWriter和PipedReader为例子总结了使用步骤,PipedOutputStream和PipedInputStream类似。
(1)Set up the connect

1
2
3
PipedReader r = new PipedReader();
PipedWriter w = new PipedWriter();
w.connect(r);

其中bufferSize默认为1024,单位为char,也就是1024个char。buffer = new char[pipeSize];
如果我们需要更改这个bufferSize,我们需要在consumer端来更改,比如new PipedReader(1024*4);

(2)Pass the reader to a processing thread

1
Thread t = new MyReaderThread(r);

(3)Transfer data

1
2
w.write(‘A');
int result = r.read();

注意,这里返回的是int型,returns the character as an integer value。
关于write,还有一个flush的方法,可以通知consumer thread,新数据到达。

1
2
3
4
5
6
7
8
w.write('A');
w.flush();

int i;
while((i = reader.read()) != -1){
char c = (char) i ;
//继续处理接收到的数据
}

(4)close the connection

1
2
w.close()
r.close()

write关闭以后,pipe就将连接断掉了,但是在buffer中的数据还是可读的,当reader关闭了以后,buffer就被清空了。

2.Shared Memory
共享内存(heap)是线程之间传递数据的常用方式。在函数中定义的基本类型变量和对象的引用变量都存放在栈中,而其余像整个类的静态变量(class member variable)和实例变量(instance member variable)以及函数中的对象都存放在heap中。栈内存归属于单个线程,每个线程都拥有一个栈内存,其存储的变量只能在其所属的线程中可见,而堆内存中的对象对所有线程可见,这就实现了线程间数据的传递。

3.Signal
Signal的方式,就是使用notify和wait的方式,这里就不再赘述使用了,需要注意的点是notifyAll方法,它会唤醒所有的等待者,等待者执行时,不能保证之前没有其他其他等待者已经执行,所以需要在执行之前先判断是否符合条件,不符合执行条件,仍然需要wait。

1
2
3
4
5
6
synchronized(this){
while(条件不满足){
wait();
}
//条件满足时,再进行处理
}

4.BlockingQueue
BlockingQueue可以理解为对Signal的封装,用来处理消费者和生产者问题,其不需要我们使用wait和notify来通知是否可以继续生产产品或者消费产品。如果BlockingQueue已经满了,生产者就无法自动生产产品(put方法就被阻塞),如果BlockingQueue为空,消费者就自动无法消费产品(take方法就被阻塞)

如果在Android中使用上面说的这四种方式,那么会造成ui线程的等待和阻塞。为此,Android自定义了一套线程间通信的系统,也就是Handler、Looper、MessageQueue。这里的内容可以参考我的这篇博客

参考文献
[1]http://www.androiddesignpatterns.com/2014/01/thread-scheduling-in-android.html
[2]https://book.douban.com/subject/25900200/