多线程编程之线程间通信机制:wait/notify机制

508 阅读9分钟

小知识,大挑战!本文正在参与“   程序员必备小知识   ”创作活动

作者的其他平台:

| CSDN:blog.csdn.net/qq_4115394…

| 掘金:juejin.cn/user/651387…

| 知乎:www.zhihu.com/people/1024…

| GitHub:github.com/JiangXia-10…

| 公众号:1024笔记

本文大概8499字,读完共需20分钟

1 前言

在多线程编程中往往需要多个线程之间进行通信从而完成一个复杂的系统。比如有两个线程,线程A和线程B,线程B的执行需要等到线程A的反馈信息,如果满足了条件则线程B执行,否则线程B进行等待。这就是线程间等待和通知机制

这就是类似于饭店中的服务员和厨师之间的关系,服务员需要上菜,但是需要等到厨师做好菜之后才能上菜,才有菜上。这种情况就有两种解决方式,要么服务员每隔一定的时间就询问一下厨师菜做好了没有,这种情况就是轮询机制;要么等待厨师主动通知,现在很多的饭店的厨房会有一个铃铛,厨师做好了就按一下铃铛,这时候服务员就过来拿菜。

对于第一种轮询机制虽然解决了通信的问题,但是可以发现有很多的弊端,一需要不断的询问明显对于时间和精力上消耗有点大,服务员没时间做其他的事情,厨师也不能专心做菜,在系统中就会造成CPU的资源占用过高,另一个问题就是轮询时间的问题了,如果时间间隔过短,那么对于资源的占用更高了,如果时间太长,很可能菜做好了都冷了还没有上,在代码中就不能及时得到更新数据,这样的系统肯定是不合格的。

2 前言

针对上面的问题,这时就可以通过wait/notifty机制来实现线程间的通信。

wait方法的作用是使当前正在执行的线程进入等待状态,wait方法是Object类的方法,该方法用来将当前线程放入到预执行队列中,并且在wait所在的代码行进行停止执行,直到接到通知或被中断为止。在调用wait方法之前,线程必须获取得该对象的对象锁,也就是说只能在同步方法或同步代码块中调用wait方法。如果在执行wait方法后,当前线程锁会自动释放。当wait方法返回线程与其它线程重新竞争获得锁。

比如:

package com.jiangxia.chap3;

public class Demo01 {
    public static void main(String[] args) {
        try {
            String str = new String();
            System.out.println("同步代码块前");
            synchronized (str){
                System.out.println("同步代码块后");
                str.wait();
                System.out.println("wait之后");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

图片

可以发现wait方法执行之后的代码并没有执行到了。

wait方法是等待,等待到什么时候?这时候就需要通知了,就像红绿灯。红灯的时候等待,绿灯亮的时候就可以过马路了。这里通知的方法就是notify

notify方法也要在同步方法或同步代码块中使用,在调用前线程必须获得该对象的对象锁,如果没有获取重适当的锁也会抛出IllegalMonitorStateException。这个方法是用来通知那些可能等待锁对象的其它线程,如果有多个线程等待,由线程调试器随机挑选一个在wait状态的线程,向其发出通知,并使等待获取该对象的对象锁。

在执行notify方法后,当前线程不会马上释放该对象锁,wait状态的线程也不能马上获取取该对象锁,要等到执行notify方法的线程将任务执行完成后,也就是退出synchronize代码块后,当前线程才会释放锁,wait状态线程才可以获取到锁。

当第一个获得该对象锁的wait线程运行完成后,它会释放掉该对象锁,如果该对象没有再次使用nofity语句,则对象处理空闲状态,其它 wait状态的线程由于没有得到通知,还会继续处理阻塞的wait状态,直到这个对象发出通知。

package com.jiangxia.chap3;

public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new DemoThread1(lock);
        t1.start();
        Thread.sleep(2000);
        Thread t2 = new DemoThread2(lock);
        t2.start();
    }
}

class DemoThread1 extends Thread{
    private Object lock;

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

    @Override
    public void run() {
        synchronized (lock){
            try {
                System.out.println("线程一开始等待"+System.currentTimeMillis());
                lock.wait();
                System.out.println("线程一结束等待"+System.currentTimeMillis());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class DemoThread2 extends Thread{
    private Object lock;

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

    @Override
    public void run() {
        try {
            synchronized (lock){
                System.out.println("线程二发出通知"+System.currentTimeMillis());
                lock.notify();
                System.out.println("线程二结束通知"+System.currentTimeMillis());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果如下:

图片

可以发现wait使线程停止运行,notify使停止的线程继续运行。

并且wait方法自动释放锁与notify方法不会释放锁,而notify方法必须执行完同步代码后才会释放锁。

调用notify方法一次只随机通知一个线程进行唤醒。如果有多个线程,可以调用多次notify方法唤醒所有的线程,不能保证系统中确定有多少个线程,也就是说如果notify方法的调用次数小于线程数量时,会出现有部分线程无法被唤醒。所以为了唤醒全部的线程,可以使用notifyAll方法。比如:

package com.jiangxia.chap3;
public class Demo03 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Demo05ThreadA(lock);
        t1.setName("A");
        t1.start();
        Thread t2 = new Demo05ThreadA(lock);
        t2.setName("B");
        t2.start();
        Thread t3 = new Demo05ThreadA(lock);
        t3.setName("C");
        t3.start();
        Thread.sleep(1000);
        Thread t4 = new Demo05ThreadB(lock);
        t4.start();
    }
}
class Demo05Service{
    public void foo(Object lock){
        try{
            synchronized (lock){
                System.out.println(Thread.currentThread().getName() + "进入了foo方法,准备执行wait方法");
                lock.wait();
                System.out.println(Thread.currentThread().getName() + "结束了foo方法");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
class Demo05ThreadA extends Thread{
    private Object lock;
    public Demo05ThreadA(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        Demo05Service service = new Demo05Service();
        service.foo(lock);
    }
}
class Demo05ThreadB extends Thread{
    private Object lock;
    public Demo05ThreadB(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            // notify仅随机唤醒一个线程
//            lock.notify();
            // 如果要唤醒多个线程,需要多次调用notify方法
//            lock.notify();
//            lock.notify();
//            lock.notify();
//            lock.notify();
            // 如果要唤醒多个线程,可以调用notifyAll方法
            lock.notifyAll();
        }
    }
}

结果如下:

图片

wait(long):带一个long参数的方法的作用等待某一时间内是否有线程对象锁进行唤醒,如果超过这个等待时间线程会自动唤醒。

wait(long)与sleep(long)的用法比较相似,都是在指定的时间后线程会自动唤醒,区别在于sleep是不会释放对象锁,而wait方法可以释放对象锁。比如:

public class Demo04 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Demo04ThreadA(lock);
        t1.start();
        Thread.sleep(5000);
        Thread t2 = new Demo04ThreadB(lock);
        t2.start();
    }
}

class Demo04ThreadA extends Thread{
    private Object lock;
    public Demo04ThreadA(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        try{
            synchronized (lock){
                System.out.println("进入同步代码块于" + System.currentTimeMillis());
                lock.wait(3000);
                System.out.println("结束同步代码块于" + System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class Demo04ThreadB extends Thread{
    private Object lock;
    public Demo04ThreadB(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println("开始唤醒线程在" + System.currentTimeMillis());
            lock.notify();
            System.out.println("结束唤醒线程在" + System.currentTimeMillis());
        }
    }
}

结果如下:

图片

在使用wait/notify时,需要注意当wait等待的条件发生了变化,很容易会造成程序逻辑的混乱。比如:

package com.jiangxia.chap3;
import java.util.ArrayList;
import java.util.List;
/*
wait的条件发生变化
 */
public class Demo06 {
        public static void main(String[] args) throws InterruptedException {
            Demo06Service service = new Demo06Service();
            Thread t1 = new Demo06ThreadB(service);
            t1.start();
            Thread t2 = new Demo06ThreadB(service);
            t2.start();
            Thread.sleep(1000);
            Thread t3 = new Demo06ThreadA(service);
            t3.start();
        }
    }

    class Demo06Service{
        private List list = new ArrayList();
        private Object lock = new Object();

        public void add(){
            synchronized (lock){
                list.add("a");
                lock.notifyAll();
            }
        }
        public void subtrac(){
            try {
                synchronized (lock) {
                    if (list.size() == 0) {
                        System.out.println(Thread.currentThread().getName() + "开始等待数据");
                        lock.wait();
                        System.out.println(Thread.currentThread().getName() + "结束获取数据等待");
                    }
                    if (list.size() > 0) {
                        list.remove(0);
                    }
                    System.out.println(Thread.currentThread().getName() + ":list的大小是" + list.size());
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
    class Demo06ThreadA extends Thread{
        private Demo06Service service;
        public Demo06ThreadA(Demo06Service service){
            this.service = service;
        }

        @Override
        public void run() {
            service.add();
        }
    }
    class Demo06ThreadB extends Thread{
        private Demo06Service service;
        public Demo06ThreadB(Demo06Service service){
            this.service = service;
        }

        @Override
        public void run() {
            service.subtrac();
        }
    }

结果如下:

图片

前面提到了java线程中有个interrupt方法可以中断线程,那么当线程呈wait状态时,调用线程对象的interrupt方法会如何?

package com.jiangxia.chap3;

public class Demo05 {
        public static void main(String[] args) throws InterruptedException {
            Object lock = new Object();
            Thread t1 = new Demo05Thread(lock);
            t1.start();
            Thread.sleep(2000);
            t1.interrupt();
        }
}

class Demo05Service{
    public void foo(Object lock){
        try{
            synchronized (lock){
                System.out.println("准备开始等待");
                lock.wait();
                System.out.println("结束等待");
            }
        }catch (InterruptedException e){
            System.out.println("出现异常,因为wait状态的线程被interrupt了,所以出现中断的异常");
            e.printStackTrace();
        }
    }
}

class Demo05Thread extends Thread{
    private Object lock;
    public Demo05Thread(Object lock){
        this.lock = lock;
    }

    @Override
    public void run() {
        Demo05Service service = new Demo05Service();
        service.foo(lock);
    }
}

结果如下:

图片

可以发现当线程呈wait状态时,调用线程对象的interrupt方法会产生InterruptedException异常。所以这两种方法不能组合使用。

wait和notify是等待与通知的情况,那么如果notify通知过早会如何呢?

package com.jiangxia.chap3;

public class Demo07 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Demo07Object canRun = new Demo07Object();
        Thread t1 = new Demo07ThreadA(lock, canRun);
        Thread t2 = new Demo07ThreadB(lock, canRun);
        // 正常执行顺序
//        t1.start();
//        t2.start();
        // 增加了同步代码块是否可以运行的判断
        t2.start();
        Thread.sleep(100);
        t1.start();
        t1.start();
        Thread.sleep(100);
        t2.start();
    }
}

class Demo07Object{
    boolean canRun = true;
}

class Demo07ThreadA extends Thread{
    private Object lock;
    private Demo07Object canRun;

    public Demo07ThreadA(Object lock, Demo07Object canRun){
        this.lock = lock;
        this.canRun = canRun;
    }

    @Override
    public void run() {
        try{
            synchronized (lock){
                while(canRun.canRun) {
                    System.out.println("准备进入等待状态");
                    lock.wait();
                    System.out.println("结束等待状态");
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

class Demo07ThreadB extends Thread{
    private Object lock;
    private Demo07Object canRun;

    public Demo07ThreadB(Object lock, Demo07Object canRun){
        this.lock = lock;
        this.canRun = canRun;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println("准备执行唤醒方法");
            lock.notify();
            System.out.println("结束唤醒方法");
            canRun.canRun = false;
        }
    }
}

结果如下:

图片

这就类似于厨师做菜,菜还没有做好就通知服务员上菜,这肯定是不行的,这里就报了IllegalThreadStateException异常。

3 总结

以上就是多线程编程中关于线程的通信机制----wait/notify。

通过上面的例子可以总结出:

1)、线程执行完同步代码块就会释放对象锁;

2)、在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放;

3)、在执行同步代码块的过程中,执行了锁所属对象的wait 方法,这个线程会释放对象锁,而此线程对象会进行线程等待池中等待被唤醒。

4)、wait(long)可以指定线程等待多少时间后自动唤醒,不需要使用notify进行通知

5)、notifyall可以通知唤醒多个等待的线程

相关推荐: