Android 当中的多线程之 wait、sleep、notify、join、yield

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继续执行….

猜你喜欢

转载自blog.csdn.net/xiao6gui/article/details/80827094