Android 当中的多线程实际上就是 Java SE 中的多线程,只是为了方便使用,Android 封装了一些类,比如 AsyncTask, HandlerThread等。今天我们总结一下 Android 当中的多线程基础知识。
多线程的实现 Runnable 和 Thread
对于 Android 多线程,我们最早学习到的都是 Thread 和 Runnable ,通常我们使用如下代码来开启一个新的线程:
public void startNewThread() {
new Thread(){
@Override
public void run() {
super.run();
// 执行耗时操作
}
}.start();
}
或者是
public void startNewThreadWithRunnable() {
new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
}
}).start();
}
实际上这两种写法的差别不大,那么 Thread 和 Runnable 有什么区别呢?
实际上 Thread 也是一个 Runnable,它实现了 Runnable 接口,内部包含了一个 Runnable 类型的 target 表示要在这个子线程执行的操作
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
/* The group of this thread */
private ThreadGroup group;
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
* @param target
* the object whose run method is invoked when this thread
* is started. If null, this classes run method does
* nothing.
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
// 初始化 Thread 并且将该 Thread 添加到 ThreadGroup 中
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
Thread parent = currentThread();
if (g == null) {
// 如果当前 ThreadGroup 参数为 null,则获取当前线程的线程组
g = parent.getThreadGroup();
}
// 添加到线程组的未执行 list
g.addUnstarted();
this.group = g;
// 设置带执行 Runnable target
this.target = target;
this.priority = parent.getPriority();
this.daemon = parent.isDaemon();
setName(name);
init2(parent);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
tid = nextThreadID();
}
public synchronized void start() {
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
started = false;
try {
// 调用 native 函数启动新的线程
nativeCreate(this, stackSize, daemon);
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
}
由此可知,实际上最终被线程执行的任务是 Runnable , 而非 Thread 。Thread 只是对 Runnable 进行了一定的封装,并且通过一些状态对 Thread 进行管理和调度。
Runnable 接口定义了可执行的任务,它只有一个无返回值的 run() 方法
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
当启动一个线程的时候,如果 target 不为空,就执行 Runnable 的 run() 方法,否则执行 Thread 自身的 run() 方法
线程的 wait , sleep ,join,yield
方法名 | 作用 |
---|---|
wait() | 当一个线程执行到 wait()方法时,它就进入到一个和该对象现关的等待池中,同时失去(释放)了对象的机锁,使得其他线程可以访问。用户可以使用 notify、notifyAll或者指定睡眠时间来唤醒当前等待池的线程。 注意:wait()、notify()、nofityAll() 必须放在 synchronized block 中,否则会抛出异常 |
sleep | 该函数是 Thread 的静态函数,作用是使调用线程进入睡眠状态。因为 sleep()是 Thread 类的 static 方法,因此它不能改变对象的机锁。所以,当在一个 Synchronized block 中调用 Sleep()方法时,线程虽然休眠了,但是对象的机锁没有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。 |
join | 等待目标线程执行完成之后再继续执行 |
yield | 线程礼让。目标线程由运行状态转换为就绪状态,也就是让出执行权限,让其他线程得以优先执行,但其他线程能否优先执行是未知的。 |
wait \ notify 、notifyAll 的使用
private void waitAndNotify() {
Log.e(TAG, "waitAndNotify: \"主线程运行\"" );
new WaitThread().start();
long startTime = System.currentTimeMillis();
synchronized (sObject) {
try {
Log.e(TAG, "waitAndNotify: \"主线程等待\"" );
// 让主线程进入等待状态
sObject.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long waitTime = System.currentTimeMillis()- startTime;
Log.e(TAG, "waitAndNotify: 主线程等待耗时 "+ waitTime+" ms");
}
static class WaitThread extends Thread {
@Override
public void run() {
super.run();
try {
synchronized (sObject) {
Thread.sleep(5000);
// 唤醒 sObject 所在的主线程
sObject.notifyAll();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在 waitAndNotify() 方法中,启动了一个 WaitThread 线程,在该线程中调用 sleep() 方法使线程睡眠 5 秒。线程启动以后,在主线程调用 sObject 的 wait() 方法,使主线程进入等待状态,此时将不会继续执行,直到 WaitThread 从 5 秒睡眠中醒过来,调用 sObject 的 wait() 方法唤醒主线程,程序继续往下走。因此得到如下结果:
waitAndNotify: "主线程运行"
waitAndNotify: "主线程等待"
waitAndNotify: 主线程等待耗时 5000 ms
wait、notify 通常用于等待机制的实现,当条件未满足的时候,调用 wait 进入等待状态,一旦条件满足,调用 notify、notifyAll 唤醒等待的线程继续执行。
join的使用
join: 阻塞当前调用 join 方法所在线程,直到接收线程执行完毕之后再继续。
private void joinTest() {
Thread thread1 = new Thread("thread_1"){
@Override
public void run() {
super.run();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "run: thread_1" );
}
};
Thread thread2 = new Thread("thread_2"){
@Override
public void run() {
super.run();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "run: thread2");
}
};
Log.e(TAG, "joinTest: 主线程启动");
try {
thread1.start();
thread1.join();
thread2.start();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "joinTest: 主线程继续执行" );
}
执行结果:
MainActivity: joinTest: 主线程启动
MainActivity: run: thread_1 // 主线程等待线程1执行完成
MainActivity: run: thread2 // 主线程等待线程2执行完成
MainActivity: joinTest: 主线程继续执行
yield使用
yield:调用该方法的线程让出执行时间给其他已经准备就绪的线程。我们知道,线程的执行是有时间片的,每个线程轮流占用 CPU 固定的时间片,执行周期到了之后,就让出执行权给其他的线程。 yield 就是主动让出线程的执行权给其他的线程,其他线程能否得到优先执行权就得看各个线程的状态了。
static class YieldThread extends Thread{
public YieldThread(String name) {
super(name);
}
@Override
public void run() {
super.run();
for (int i = 0;i<5;i++) {
Log.e("yieldThread", getName()+", 优先级为:"+getPriority()+" ----->"+ i );
if (i % 2 == 0) {
// 让出线程执行权
yield();
}
}
}
}
...
private void yieldTest() {
YieldThread yieldThread1 = new YieldThread("thread_1");
YieldThread yieldThread2 = new YieldThread("thread_2");
yieldThread1.start();
yieldThread2.start();
}
执行结果:
thread_1, 优先级为:5 ----->0
thread_2, 优先级为:5 ----->0
thread_1, 优先级为:5 ----->1
thread_1, 优先级为:5 ----->2
thread_2, 优先级为:5 ----->1
thread_2, 优先级为:5 ----->2
thread_1, 优先级为:5 ----->3
thread_1, 优先级为:5 ----->4
thread_2, 优先级为:5 ----->3
thread_2, 优先级为:5 ----->4
线程1首先执行,执行完一次以后,让出执行权,线程2继续执行,执行完一次以后让出执行权,线程1继续执行….