从Java线程到kotlin协程之线程同步(wait)

Posted XeonYu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Java线程到kotlin协程之线程同步(wait)相关的知识,希望对你有一定的参考价值。

上一篇:
从Java线程到kotlin协程之线程同步(synchronized关键字)

wait

wait跟sleep方法的作用一样,也是让线程休眠,但是针对的对象不同。

  • sleep: sleep是Thread类中的静态方法,是直接作用于线程的,在哪个线程调sleep,哪个线程就休眠,而且可以随时调用。sleep方法执行期间不会释放锁,其他线程也必须要等待sleep执行完毕后才有机会进入同步代码中。
  • wait: wait是Object类中的一个普通方法,通过对象调用。但是必须在持有该对象锁的同步代码中调用,否则会抛出异常。wait方法会释放锁,其他线程可以在wait期间进入同步代码中。

我们先来看看Object对象中的方法,本篇博客需要关心的就是以下红框中的几个方法。

在这里插入图片描述

再来看看wait源码
在这里插入图片描述

可以看到,最终调用了重载的 wait(long timeout)

在这里插入图片描述

我们先来简单用一用,看一下跟sleep的区别:

    public static void main(String[] args) {
        Object o = new Object();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (o) {

                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    try {

                        TimeUnit.SECONDS.sleep(2);
//                        o.wait(2000);
                        System.out.println(Thread.currentThread().getName() + "被唤醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "执行完毕");

                }
            }).start();

        }

    }

这里我们先在5个线程中的同步代码块中使用sleep看一下:
在这里插入图片描述

同样的代码,把sleep改成wait试一下,这要注意wait方法必须要在持有对象锁的同步代码块中调用,也就是说你想调用o.wait,那么你必须先锁住o。

    public static void main(String[] args) {
        Object o = new Object();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (o) {

                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    try {

//                        TimeUnit.SECONDS.sleep(2);
                        o.wait(2000);
                        System.out.println(Thread.currentThread().getName() + "被唤醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "执行完毕");

                }
            }).start();

        }

    }

运行结果如下:
在这里插入图片描述

对比两个运行结果,可以很明显的看出来,sleep是不会释放锁的,休眠期间其他线程必须等待锁被释放后才能执行,wait是会释放锁的,其他线程会在wait期间走进同步代码中。

上面我们是给wait指定了等待时间,当我们不给wait指定时间时,则线程会一直处于等待状态,直到调用了同一个对象锁的notifynotifyAll方法,才会继续执行。

示例代码:
不给wait指定等待时间

    public static void main(String[] args) {
        Object o = new Object();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (o) {

                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    try {
                        o.wait();
                        System.out.println(Thread.currentThread().getName() + "被唤醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "执行完毕");

                }
            }).start();

        }

    }

运行结果:
在这里插入图片描述
可以看到,后面一直都没执行。

notify

源码注释太长,这里就不贴了。
我们只需要记住,notify是用来唤醒正在等待的单个线程,如果同一把锁有多个线程都在等待,则随机唤醒其中一个。
我们来简单用一用:

    public static void main(String[] args) {
        Object o = new Object();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (o) {

                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    try {
                        o.wait();
                        System.out.println(Thread.currentThread().getName() + "被唤醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "执行完毕");

                }
            }).start();

        }


        /*让主线程休眠2秒钟*/
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized (o) {
            o.notify();
        }

    }

看一下运行结果:

在这里插入图片描述

可以看到,只有一条线程被唤醒了。

notifyAll

notify是用来唤醒正在等待的所有线程,如果同一把锁有多个线程都在等待,则唤醒所有线程。

来用一把:

    public static void main(String[] args) {
        Object o = new Object();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (o) {

                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    try {
                        o.wait();
                        System.out.println(Thread.currentThread().getName() + "被唤醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + "执行完毕");

                }
            }).start();

        }


        /*让主线程休眠2秒钟*/
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized (o) {
            o.notifyAll();
        }

    }

运行结果如下:
在这里插入图片描述

可以看到,所有wait的线程都被唤醒了。

实际上,Thread中的join方法底层调用的就是wait方法,join源码如下,join本身就是个同步方法。

在这里插入图片描述
那join方法是什么时候会被唤醒呢?

在这里插入图片描述
通过注释可以知道,是在线程处于结束状态后就会被唤醒。

之前我们在 从Java线程到kotlin协程之线程合并 (join) 这篇文章中我们用join来实现两个接口

那这样一来,我们可以使用wait来达到跟join一样的效果,实现线程的同步。
代码如下:

先来创建两个Runnable来模拟请求接口


/**
 * 模拟请求高德接口
 */
class GaoDeRunnable implements Runnable {
    /*模拟的锁对象*/
    Object lock = null;

    /*接口返回的结果*/
    private String result = "";

    public GaoDeRunnable(Object lock) {
        this.lock = lock;
    }

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    @Override
    public void run() {
        /*模拟请求高德接口*/

        synchronized (lock) {
            System.out.println("高德请求开始执行....");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            result = "gaodeResult";

            System.out.println("高德接口请求完毕,唤醒等待的线程");

            /*执行完毕  唤醒等待的线程*/
            lock.notifyAll();
        }

    }


}


/**
 * 模拟请求百度接口
 */
class BaiDuRunnable implements Runnable {
    Object lock = null;

    private String result = "";

    public BaiDuRunnable(Object lock) {
        this.lock = lock;
    }

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    @Override
    public void run() {
        /*模拟请求高德接口*/

        synchronized (lock) {
            System.out.println("百度请求开始执行....");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            result = "baiduResult";

            System.out.println("百度接口请求完毕,唤醒等待的线程");

            lock.notifyAll();
        }

    }


}






两个代码逻辑都一样,休眠一段时间后,唤醒wait的线程。

main方法代码如下:

  
    public static void main(String[] args) {

        /*高德 锁*/
        Object gaodeObjLock = new Object();
        /*百度 锁*/
        Object baiduObjLock = new Object();

        /*模拟请求高德接口的Runnable*/
        GaoDeRunnable gaoDeRunnable = new GaoDeRunnable(gaodeObjLock);
        /*模拟请求百度接口的Runnable*/
        BaiDuRunnable baiDuRunnable = new BaiDuRunnable(baiduObjLock);

        /*启动两个线程去执行*/
        new Thread(gaoDeRunnable).start();
        new Thread(baiDuRunnable).start();

        /*等待高德Runnable返回数据*/
        synchronized (gaodeObjLock) {
            while (gaoDeRunnable.getResult().isEmpty()) {
                try {
                    gaodeObjLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            /*高德接口执行完毕*/
            System.out.println("高德接口执行完毕,获取的结果是:" + gaoDeRunnable.getResult());

        }

        /*等待百度Runnable返回数据*/
        synchronized (baiduObjLock) {
            while (baiDuRunnable.getResult().isEmpty()) {
                try {
                    baiduObjLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }


            /*高德接口执行完毕*/
            System.out.println("百度接口执行完毕,获取的结果是:" + baiDuRunnable.getResult());
        }


        System.out.println("两个接口都执行完毕了,最终的数据是:" + gaoDeRunnable.getResult() + "-----" + baiDuRunnable.getResult());

    }

下面来看看运行效果:

在这里插入图片描述

可以看到,运行结果跟之前使用join是一样的。

好了,wait方法就是这些


如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

以上是关于从Java线程到kotlin协程之线程同步(wait)的主要内容,如果未能解决你的问题,请参考以下文章

从Java线程到kotlin协程之线程的状态

从Java线程到kotlin协程之线程合并 (join)

从Java线程到kotlin协程之线程休眠 (sleep)

从Java线程到kotlin协程之线程的基本使用

从Java线程到kotlin协程之多线程的基本概念

Kotlin SharedFlow&StateFlow 热流到底有多热?