一. 为什么要了解进程/线程
从操作系统的角度,进程是操作系统管理的一个实体,操作系统为其定义了虚拟地址空间,进程之间的数据调度等。从应用开发的角度,我们写的程序就必须运行在一个进程实例中。同时对于进程,如果采用了多线程方法,也涉及到多线程的管理。所以学习并掌握一个平台,了解进程/线程是十分基础且必须的一件事情。
二 . 进程
一般来说,一个程序包对应于一个进程。(当然,这也不是不可改变的,我们可以通过NDK提供的本地接口,来fork子进程。)多个Activity共享一个主线程(ActivityThread),Service也是寄存于ActivityThread中的。Activity和Service一样,他们启动是都需要两个Binder线程的支持。
三. 线程
对于一个Activity必定会包含一个main thread,此外还有可能有一些binder thread。同时Zygote系统进程负责为每个新启动的应用fork新的进程。此外,Zygote为Activity创建的主线程是ActivityThread。
四. Handler,MessageQueue,Looper,ActivityThread, Thread
Looper不断从MessageQueue中获取Message(或者是Runnable),然后交由Handler处理。
Handler。(frameworks/base/core/java/android/os/Handler.java)
使用示例:(在主线程中创建一个Handler,在子线程中发送Message。后面有在子线程中使用Handler的例子)
定义handler
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
popupWindowLogo.showAtLocation(logoPop,
Gravity.CENTER_HORIZONTAL, 10, 60);
break;
case 1:
popupWindowWelcome.showAtLocation(welcomePop,
Gravity.CENTER_HORIZONTAL, 10, 100);
break;
case 2:
if (null != popupWindowLogo && popupWindowLogo.isShowing())
popupWindowLogo.dismiss();
if (null != popupWindowWelcome
&& popupWindowWelcome.isShowing())
popupWindowWelcome.dismiss();
break;
case 3:
initUserInfo();
default:
break;
}
}
};
定义Thread
class PauseRun implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
handler.sendEmptyMessage(0);
Thread.sleep(1000);
handler.sendEmptyMessage(1);
Thread.sleep(1000);
handler.sendEmptyMessage(2);
Thread.sleep(1000);
handler.sendEmptyMessage(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
分析如下:
1)Handler将消息压入MessageQueue中。以上示例使用的是sendEmptyMessage()函数,除此之外,send系的函数还有sendMessageAtFrontOfQueue(Message msg); sendMessageAtTime(Message msg, long uptimeMillis); sendMessageDelayed(Message msg, long delayMillis);等。对应的还有post系的函数,包括post(Runnable r), postAtTime(Runnable r, long uptimeMillis)。
send和post二者主要的区别在于,send传递的直接是Message,而post传递的是其他类型的消息,如Runnable。post会先将该信息转换为Message,然后在通过send系的函数(先调用sendMessageDelayed,再调用sendMessageAtTime将消息压入MessageQueue)。
2)Thread和Handler的关系。从以上示例中,可以看出一个Thread可以对应于多个Handler。同样的,也可以从Thread类和Handler类之间的关系中看出
Looper{
MessageQueue mQueue;
Thread mThread;
}
Handler{
MessageQueue mQueue;
Looper mLooper;
Callback mCallback;
}
MessageQueue{
Message mMessages;
}
Message{
Handler target;
}
3)我们在Thread中不能直接操作UI元素,只能在Handler中进行处理。所以Handler也给我们提供了一种在多线程环境下操作UI元素的方式。
MessageQueue。(frameworks/base/core/java/android/os)
MessageQueue主要负责维护消息队列,其中提供了对该队列的各种操作:新建(构造函数+nativeInit()),元素入队(enqueueMessage(Message msg, long when)),元素出队(next()),删除元素(removeMessage(Handler h, int what, Object object)),销毁队列(removeMessage(Handler h, Runnable r, Object object))。
Looper。(frameworks/base/core/java/android/os)
Looper.prepare()源码如下:
public static void prepare() {
prepare(true);//true表示可以退出; ActivityThread中调用的是perpareMainLooper(), 其中会调用perpare(false)。说明ActivityThread的loop不能退出。
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
注:其中sThreadLocal的定义如下:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ThreadLocal可以为每个线程维护变量,并使其成为该线程相关的独立副本。所以Looper利用ThreadLocal使得每个线程拥有彼此独立的Looper实例。
Looper.loop()源码如下:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycle();
}
}
其中myLooper()用来获取在prepare()中建立的本线程相关的Looper实例:
public static Looper myLooper() {
return sThreadLocal.get();
}
Handler类中存在下面几个成员变量:
Handler{
final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;
}
我们在建立Handler实例的时候,就已经给这几个变量赋值了:
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
所以,通过如下代码,就能使Looper, MessageQueue, Handler联系起来:(一般开发人员也是这么在用)
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
ActivityThread。(frameworks/base/core/java/android/app/ActivityThread.java)
ActivityThread是应用程序的主线程。在该主线程中主要的工作如下:
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
其中prepareMainLooper()会建立主线程相关的Looper以及MessageQueue。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
对于主线程来说,其Looper是可以全局访问的(其他线程也可以访问):在Looper类中定义了指向主线程Looper的sMainLooper对象引用。
private static Looper sMainLooper; // guarded by Looper.class
其他线程通过调用getMainLooper()函数即可访问:
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
所以,当前进程中的各个线程都可以访问主线程的消息队列。
Thread。(/*libcore/libdvm/src/main/java/java/lang/Thread.java*/)
Java中多线程采用抢占式调度。(另一种调度模式是分时调度:分时会平分CPU时间,抢占则根据优先级分配CPU时间)
synchronized。该关键字主要用于java多线程中同步,它可以保证只有一个线程执行其修饰范围内的部分。作用范围可以是一个代码块,一个方法,一个对象,一个类的字面常量(如 Person.class)(普通变量不得行)。
- 当其修饰一个对象时(synchronized(this)),只能有一个线程能执行synchronized修饰的同步代码块,其他线程须等到该线程执行完成后,才能进入并执行。对于类中的其他非同步代码块,其他线程可以访问。
- (接上)对于其他的同步代码,其他线程对其访问将处于阻塞状态。因为synchronized(this)修饰该对象,也就是说当前运行的线程获取了该对象的对象锁。其他所有线程只有得到了该对象锁才能访问该对象中的方法。
- synchronized方法块的出现是为了避免synchronized修饰一个大的方法而引发的程序运行效率降低。
- synchronized修饰对象实例和类的字面常量是不同的。后者范围更大,对于所有该类的实例都会产生同步效果,即,所有该类的实例共享一个对象锁。而前者的各个对象实例的对象锁是分离的。注:类的静态(static)synchronized函数也可以达到和类字面常量同样的效果,即,取得该类的全局锁。然而,这两种锁并不具有包含关系,他们只是作用的范围不一样,当两种同步语句作用于统一各类的不同方法时,多线程中是可以同时访问这两个方法的,即,并不具有同步关系。
//synchronized方法
public synchronized void accessVal(int newVal);
//synchronized方法块
synchronized(syncObject) {
//允许访问控制的代码
}
每个对象实例或者类都有一个锁。Synchronized关键字将某些方法,变量保护起来,防止多线程访问冲突,以免造成不可预期的程序错误。一个类(对象)可以比喻成一个房间,类(对象)中的方法好比是抽屉。synchronized方法是带锁的抽屉,所有的抽屉共用一把钥匙,且在房间门口放着,只有获得了对象锁才能访问该抽屉。所以对于想要访问带锁抽屉的人(线程)必须要先获取到门口的钥匙,等到访问抽屉结束,则要将钥匙归还,从而后面的人(线程)才可以继续访问带锁的抽屉。而非synchronized方法是不带锁的抽屉,所以大家都可以访问。
说到多线程,不得不说多线程协同工作的方法,wait(), notity(), notifyAll(), interrupt(), join(), sleep()等方法,是java中的多线程控制(同步)与调度方法,下面对他们进行详细的分析。
wait()
该方法是Object类的成员方法。也就是说java中所有的对象都可以调用此方法,类似于没有对象都对应于一个对象锁。所以每当我们谈论wait()方法都必须要跟对象锁挂钩。该线程进入等待状态后会释放该对象锁。
notify()/notifyAll()
该方法相对于wait()方法。使得等待状态的线程可以重新运行。值得注意的是,notify()方法是随机任意的选择一个在等待该object的线程来唤醒,具体看虚拟机的实现方式。而notifyAll()则是唤醒等待该object的全部线程。
以下是wait()和notifyAll()的使用示例:
程序大概意思:两个线程协同工作,操作整型num。当第一个线程对num操作到50时,线程1进入等待状态,并释放对象锁。此时线程2获得对象锁,并开始操作num,将其从100减到1。线程2完成该操作后,调用notifyAll()函数,唤醒线程1,让其完成余下工作:将num从50继续减到1。
class Third {
int num = 100;
boolean wait = true;
String str = new String();
public synchronized void noti() {
notifyAll();
wait = false;
}
public void A() {
synchronized (this) {
while (num > 0) {
System.out.println(Thread.currentThread().getName()
+ "this is " + num--);
if (num == 50 && wait)
try {
wait();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}
public void B() {
synchronized (this) {
num = 100;
while (num > 0) {
System.out.println(Thread.currentThread().getName()
+ "this is " + num--);
}
num = 50;
noti();
}
}
}
class TxtThread implements Runnable {
public TxtThread(Third obj) {
third = obj;
}
Third third;
public void run() {
third.A();
}
}
class TxtThread2 implements Runnable {
public TxtThread2(Third obj) {
third = obj;
}
Third third;
public void run() {
third.B();
}
}
public class Test {
public static void main(String args[]) throws IOException,
InterruptedException {
Third th = new Third();
TxtThread tt = new TxtThread(th);
TxtThread2 t2 = new TxtThread2(th);
new Thread(tt).start();
new Thread(t2).start();
}
}
总结:
- 对于wait(), notify(), notifyAll()的调用,必须对于同步代码,也就是说,调用这些方法前要获取到对象锁。
- 对于wait(), notify(), notifyAll()的调用,必须要对应于同一个对象锁,否则没有同步效果。
- 对于wait(), notify(), notifyAll()的调用,对象锁不能是类字面常量对应的锁。否则wait()方法会出错,同时notify()/notifyAll()方法也会出错。
interrupt()
比较好理解,此处不做说明。
join()
该方法主要用于保证线程运行的顺序。
示例如下:
public static void main(String args[]) throws IOException,
InterruptedException {
ThreadTest t1 = new ThreadTest("Thread1");
ThreadTest2 t2 = new ThreadTest2("Thread2");
ThreadTest3 t3 = new ThreadTest3("Thread3");
t3.start();
t3.join();
t2.start();
t2.join(3000);
t1.start();
}
该例子可以保证,线程t2只有在线程t3运行结束后才能运行;线程t1会等待线程t2运行3000ms,如果超时,线程t1也会开始运行。
sleep()
也是使线程处于等待状态,于wait()不同,wait()一般是等待某个object(上面已经详细介绍过)。而sleep()则是等待一段时间,且sleep()不会释放锁(如果获得了的话)。