Android 当中的多线程实际上就是 Java SE 中的多线程,只是为了方便使用,Android 封装了一些类,比如 AsyncTask, HandlerThread等。今天我们总结一下 Android 当中的多线程基础知识。
对于 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() | 当一个线程执行到 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继续执行….