线程的结束,挂起和恢复

引言

Android的车辆监控端应用开发中,碰到了轨迹回放控制,播放、暂停、停止不知道该怎么实现的问题,虽然知道要用线程。然后找到了这篇文章,讲解的非常清晰,在此记录之,文末有原文链接。最后将整个轨迹回放实现完成后,再看代码恍然大悟,这不就是音乐播放器的播放、暂停、停止的实现,当初看播放器源码应该很容易就解决了,结果重复造了轮子,不过还是很有收获的。

PS:程序示例部分是我在原作者的基础上修改而来,感觉应该更通俗易懂些,希望能对需要的人有帮助,有什么错误的话还请指正,谢谢。

Android中的线程操作

在Android应用的开发过程中,我们有时候需要通过创建一个新的线程去完成一些任务。例如,我们去进行搜寻动作,如果搜寻比较费时,我们就需要通过进度条来提示用户搜寻的进展情况,避免用户认为发生了死机。此时进度条的刷新就需要另外一个线程去实现。

但是这里有一个误区:有些人在多线程开发的时候会错误的认为,如果我们从创建线程的Activity中退出(该Acitivity被销毁),则在该Activity中创建的自定义线程也会被销毁。其实这是大错特错了。

实践证明,上述情况下,创建的线程并不会自动销毁,而是仍然在后台默默无闻地执行,直到自行结束。Android的这种设计是无可厚非的。从理论上来解释,应用的最小执行单位是线程,最小资源单位是进程,一个进程可以包含多个线程,而多个线程共享同一个所属进程的资源。因此,个人理解Android的应用其实就是一个进程,而里面的每个UI,Activity就是从属这个进程的线程,从一个Activity进入另外一个Activity本质就是将之前的线程挂起,然后创建后面的线程。退出也是同理。自定义线程也是遵循这个原则的。除非去控制某个线程结束,否则只有当该现程执行完毕或者所属的进程被销毁,该线程才会真正的结束。

综上,当我们在自定义线程还没有执行完毕的情况下,需要结束相关动作的时候,我们就要认为地去结束相关线程。例如,在搜寻过程中,我们不想去继续搜寻,而退出了搜寻功能,此时我们就需要去结束自定义的搜寻线程。如果不这样会可能造成严重错误。例如,我们反复进入搜寻功能去搜寻,在搜寻未结束时退出,然后再进入。这种情况下,由于之前的自定义线程并未结束,而之后又会有多个新搜寻线程被创建执行,很容易导致临界区冲突,从而导致设备当机。

那么我们如何控制这些自定义线程呢?

线程的结束

其实,通过帮助文档,我们可以知道,Android的线程类本身就提供了一些公共方法去结束线程。

void destroy()

This method is deprecated. Not implemented.

synchronized final void stop(Throwablethrowable)

This method is deprecated. because stopping a thread in this manner is unsafe and can leave your application and the VM in an unpredictable state.

final void stop()

This method is deprecated. because stopping a thread in this manner is unsafe and can leave your application andthe VM in an unpredictable state.

但是,通过说明我们可以看到,这些方法Android本身都是不推荐使用的,通过这种方式结束线程是不安全的,可能会让我们的应用退出,并且会让虚拟机处于一种无法预料的状态。那么开发过程中,在合情合理的需求中,我们怎么去安全的结束指定的自定义线程呢?

解决方法

  1. 我们可以在自定义线程类中定义一个布尔私有变量,并且初始化为假,用于记录线程的执行状态。

  2. 在run函数开始,设置该变量为真,表示线程进入执行状态。

  3. 在run函数结束位置,设置该变量为假,表示线程进入结束状态。

  4. 在run的线程执行部分,我们可以找一些锲点,对该变量进行判断,如果为真则继续执行,否则退出run函数。

  5. 在自定义线程类中再提供一个公共函数,该函数的作用是将上述状态变量设置为假。

这样,当自定义线程执行还未结束时,我们就可以通过调用5中的方法将线程安全结束。思想的本质就是,既然我们不能安全强制结束线程,那我们就让它安全地提前退出。效果是一样的。

程序实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class SearchThread extends Thread {

private boolean isRun = false;

public SearchThread() {
isRun = false;
}

public void setStopState() {
isRun = false;
}

@Override
public void run() {

//改变为运行状态
isRun = true;
//开始运行
for (int i = 0; i < 100; i++) {
//每次运行检查运行标志位
if (!isRun) {
return;
}

// TODO Something
}

}
}

线程的挂起与恢复

同样,Android线程类也提供了一些公共方法去挂起和恢复线程:

final void resume()

This method is deprecated. Used with deprecated method suspend.

final void suspend()

This method is deprecated. May cause deadlocks.

同样不幸的是,通过说明我们可以看到,这些方法Android也是不推荐使用的,经过笔者试验,这些方法也没有效果。那我们如何去挂起和恢复线程呢?

Android的类基本都是继承于Object类。此类可以说是Android类的祖先了。如果把Android类比喻为一个生物界,则Object类就是当初生命诞生时候的那个单细胞。

我们可以发现Object类提供了几个方法:

final void notify()

Causes a thread which is waiting on this object’smonitor (by means of calling one of the wait() methods) to be woken.

final void notifyAll()

Causes all threads which are waiting on thisobject’s monitor (by means of calling one of the wait() methods) to be woken.

final void wait()

Causes the calling thread to wait until another thread calls the notify() or notifyAll() method of this object.

通过说明我们可以知道,wait方法可以让正在调用的线程处于等待状态,直到其他线程调用了该对象的notify或者notifyAll,而notify和notifyAll方法则是用于唤醒处于等待中的线程。

同样,线程类也是继承于Object类,但是线程类是一个比较特殊的类,有自己独立的栈机制来处理其方法,参数和局部变量。通过实验发现,虽然线程类继承于Object类,但是却不能通过wait和notify方法挂起唤醒线程。而要实现上述动作,必须去间接地实现,即在自定义线程类中创建一个Object对象,然后通过对该Object的相关操作实现线程的挂起和唤醒。

解决方法

  1. 在自定义线程类的实例化过程中创建一个Object对象。

  2. 定义一个变量来记录线程的状态是否挂起,初始化为假。

  3. 在线程类中的run函数中的线程执行部分找入锲点来执行下面动作:如果当前状态变量为假(表示线程挂起),则通过1中Object对象的wait方法挂起当前线程,即线程暂停在锲点位置,如果被唤起,则将从锲点后继续执行。

  4. 定义一个方法来修改线程状态变量为真,从而达到挂起线程的目的。

  5. 定义一个方法去唤醒线程。判断如果线程状态变量为真,则修改其为假,然后调用1中Object对象的notifyAll方法唤醒对象。(notify方法也可以,但是如果自定义线程较多时容易造成死锁)。

综上,当自定义线程运行后我们可以通过4中的方法挂起线程,通过5中的方法唤醒线程。

程序实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class SearchThread extends Thread {

private Object mPauseLock;
private boolean mPauseFlag;

public SearchThread() {
mPauseLock = new Object();
mPauseFlag = false;
}

public void pauseThread() {
synchronized (mPauseLock) {
mPauseFlag = true;
}
}

public void resumeThread() {
synchronized (mPauseLock) {
mPauseFlag = false;
mPauseLock.notifyAll();
}
}

@Override
public void run() {
//线程开始执行
for (int i = 0; i < 100; i++) {
//每次执行检测是否应改变为暂停状态
synchronized (mPauseLock) {
if (mPauseFlag) {
try {
mPauseLock.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}

// TODO Something
}
}
}

PS:原文链接在此。