• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

多线程-线程通讯线程的通知和唤醒wait()、notify()/notifyAll()

武飞扬头像
一个很酷的女巫_
帮助1


由于多个线程之间是抢占执行的,因此线程之间执行的先后顺序难以预知,但我们在实际的开发过程中,我们希望能够合理的协调多个线程之间的执行先后顺序。
这就引出了完成这个协调工作的三个方法:

  • wait()/wait(long timeout):让当前线程进入等待状态。
  • notify():唤醒在当前对象上等待的一个线程
  • notifyAll():唤醒在当前对象上等待的所有线程。

一、wait()方法的使用

wait()执行流程:

  • 使当前线程进入休眠状态(将线程放入等待队列中)
  • 释放当前的锁
  • 满足一定条件时被唤醒,重新尝试获取这个锁

wait要搭配synchronized来使用,如果没有配合synchronized使用wait会直接抛出异常。
wait()结束等待的条件:

  • 其他线程调用该对象的notify方法。
  • wait(long timeout)等待超时。
  • 其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedException异常。
/**
 * wait使用
 */
public class waitDemo {
    public static void main(String[] args) {
        Object lock=new Object();
        Thread t1=new Thread(()->{
            System.out.println("线程1开始执行");
                try {
                    synchronized (lock) {
                        System.out.println("线程1调用wait方法...");
                        //无限期等待
                        lock.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1执行完毕");
        },"线程1");
        t1.start();
    }
}

学新通

学新通
这样线程就会一直等待下去,所以我们需要用到唤醒方法notify()。

二、wait(long timeout)

给wait方法传入一个参数,表示等待的时间,如果超过这个时间wait就会结束等待,继续执行任务,而无参的wait方法就会一直等待,直到有线程唤醒它。
我们来看具体的代码执行效果:

import java.time.LocalDateTime;

public class waitDemo4 {
    public static void main(String[] args) {
        Object lock=new Object();
        Object lock2=new Object();
        new Thread(()->{
            synchronized (lock){
                System.out.println("线程1:开始执行");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1:执行完成");
            }
        },"无参wait线程").start();

        new Thread(()->{
            synchronized (lock2){
                System.out.println("线程2:开始执行" "|开始时间:"  LocalDateTime.now());
                try {
                    lock2.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程2执行完毕" "|结束时间:" LocalDateTime.now());
            }
        },"有参wait线程").start();
    }
}

学新通

学新通
可以看到,无参的wait()方法在一直等待,而有参的wait()在等待超时后,会继续往下执行。

三、wait() VS wait(long timeout)

共同点:

  • 无论是有参还是无参的wait()方法,都可以使线程进入休眠状态。
  • 无论是有参还是无参的wait()方法,都可以使用notify/notifyAll来进行唤醒。

不同点:

  • wait(long timeout)当线程超过设置时间后,会自动恢复执行;wait()会无限等待。
  • 使用无参的wait方法,线程会进行waiting状态;使用有参的wait方法会进入timeid_waiting状态。

四、notify()方法的使用

notify方法是唤醒等待的线程。

  • 方法notify()也需要在同步方法或者同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其他线程,对其发出notify通知,并使他们重新获取该对象的对象锁。
  • 在notify()方法后,当前线程并不会立刻释放该对象锁,而是等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放该锁。
public class waitDemo2 {
    public static void main(String[] args) {
        Object lock=new Object();
        Object lock2=new Object();

        Thread t1=new Thread(()->{
            System.out.println("线程1开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程1调用wait方法...");
                    //无限期等待
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1执行完毕");
        },"线程1");
        t1.start();


        Thread t2=new Thread(()->{
            System.out.println("线程2开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程2调用wait方法...");
                    //无限期等待
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2执行完毕");
        },"线程2");
        t2.start();

        Thread t3=new Thread(()->{
            System.out.println("线程3开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程3调用wait方法...");
                    //无限期等待
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程3执行完毕");
        },"线程3");
        t3.start();

        Thread t4=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程4:开始执行,唤醒线程");
            synchronized (lock){
                //发出唤醒通知
                lock.notify();
                System.out.println("线程4:执行唤醒操作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程4:执行完成");
            }
        },"线程4");
        t4.start();
    }
}

学新通

学新通
从上面代码的执行结果来看,可以说明我们上面提到的几个问题:①notify是可以唤醒等待当前对象的对象锁一个线程 ②在唤醒其他线程之后,并不会立刻释放锁,而是等待这个唤醒线程中的程序执行完之后才释放锁。

四、notifyAll()方法的使用

使用notifyAll()可以一次唤醒所有等待线程。
上代码看看它的使用~

public class waitDemo3 {
    public static void main(String[] args) {
        Object lock=new Object();
        Object lock2=new Object();

        Thread t1=new Thread(()->{
            System.out.println("线程1开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程1调用wait方法...");
                    //无限期等待
                    lock.wait();
                    System.out.println("线程1:恢复执行之后又进入休眠状态");
                    Thread.sleep(1000);
                    System.out.println("线程1执行完毕");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程1");
        t1.start();


        Thread t2=new Thread(()->{
            System.out.println("线程2开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程2调用wait方法...");
                    //无限期等待
                    lock.wait();
                    System.out.println("线程2:恢复执行之后又进入休眠状态");
                    Thread.sleep(1000);
                    System.out.println("线程2执行完毕");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程2");
        t2.start();

        Thread t3=new Thread(()->{
            System.out.println("线程3开始执行");
            try {
                synchronized (lock2) {
                    System.out.println("线程3调用wait方法...");
                    //无限期等待
                    lock2.wait();
                    System.out.println("线程3:恢复执行之后又进入休眠状态");
                    Thread.sleep(1000);
                    System.out.println("线程3执行完毕");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程3");
        t3.start();

        Thread t4=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程4:开始执行,唤醒线程");
            synchronized (lock){
                //发出唤醒通知
                lock.notifyAll();
                System.out.println("线程4:执行唤醒操作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程4:执行完成");
            }
        },"线程4");
        t4.start();
    }
}

学新通

通过上面的代码执行结果,我们可以确定notifyAll()可以一次唤醒等待当前对象的对象锁的所有线程。

五、为什么wait和notify必须放在synchronized中执行?

wait和notify如果不配合synchronized使用,在程序运行时就会报IllegalMonitorStateException非法的监视器状态异常,而且notify也不能实现程序的唤醒功能了。

public class waitDemo2 {
    public static void main(String[] args) {
        Object lock=new Object();
        Object lock2=new Object();

        Thread t1=new Thread(()->{
            System.out.println("线程1开始执行");
            try {
                    System.out.println("线程1调用wait方法...");
                    //无限期等待
                    lock.wait();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1执行完毕");
        },"线程1");
        t1.start();

        Thread t4=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程4:开始执行,唤醒线程");
            synchronized (lock){
                //发出唤醒通知
                lock.notify();
                System.out.println("线程4:执行唤醒操作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程4:执行完成");
            }
        },"线程4");
        t4.start();
    }
}

学新通

学新通
因此我们需要配合synchronized使用,Java这样设计是为了防止多线程并发执行时,程序执行混乱问题。所以java引入了来防止混乱问题。

那么混乱问题是什么呢?
我们来看下面这个阻塞式队列的执行状况吧~

public class ThreadQueue {
    static class MyBlockedQueue{
        private int first;//队头
        private int last;//队尾
        private int size;//队列实际存储大小
        private int[] item;//实际存储容器

        public MyBlockedQueue(int capacity){
            this.first=0;
            this.last=0;
            this.size=0;
            this.item=new int[capacity];
        }
        /**
         * 入队
         */
        public void offer(int val) throws InterruptedException {
                if(size==item.length){//当前队列已满,阻塞等待,当有元素出队时再唤醒继续执行
                    this.wait();
                }
                item[last]=val;
                last  ;
                size  ;
                //判断是否最后一个元素
                if(last==item.length-1){
                    last=0;
                }
                this.notify();        //③ 唤醒出队任务中的休眠线程
        }

        /**
         * 出队
         */
        public int poll() throws InterruptedException {
            int result=0;
                if(size==0){// ① 队列中已经没有数据了,阻塞等待入队任务中有元素入队再唤醒继续执行
                    this.wait();      //②
                }
                result=item[first];
                first  ;
                size--;
                //判断是否最后一个数据
                if(first==item.length-1){
                    first=0;
                }
                this.notify();//唤醒入队列中的休眠线程
            return result;
        }
    }
}
学新通

上面的代码中我们标识了三个重要的步骤,①判断当前队列中有无元素,若没有 执行②wait()方法等待 ,再执行③给队列中添加元素后执行唤醒操作。
但是如果不强制添加synchronized,就有可能出现以下问题:
学新通

看出问题了吗?友友们!如果不配合synchronized使用,那么线程1执行完判断之后,还没有进入休眠状态,这时时间片轮转,线程2将元素入队列,并唤醒线程1。时间片再给到线程1时,线程1已经执行过判断了,会直接进入休眠状态,从而导致队列中的那条数据就会永久性的不能被读取。这就是程序并发执行时产生的“执行混乱问题”。

但是如果配合了synchronized使用,执行流程就变成如下这样:
学新通
这样子,步骤①②一起执行,程序的执行结果就正常了,这就是我为什么java设计需要让wait和notify必须配合synchronized一起执行的原因。

六、notify真的是随机唤醒吗?

首先我们来说结论~
notify的唤醒并不是随机的,而是从等待队列中获取第一个元素进行出队,也就是顺序唤醒的。
我们来看下notify的源码:
学新通
翻译下上面的内容,就是说notify选择唤醒的线程是任意的,但是具体的实现还是要依赖于JVM厂商。

最主流的就是官方的JVM,也就是HotSpot虚拟机。他的源码实现在ObjectMonitor.cpp 中,具体源码如下:
学新通
DequeueWaiter 方法实现的源码如下:
学新通
所以这也就验证了我们上面所说的,notify的唤醒并不是随机的,而是在——waitSet中获取第一个元素进行出队唤醒。

这就是线程通讯部分的内容啦,小伙伴觉得对你有用的话就点个赞支持一下吧~

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhhahgff
系列文章
更多 icon
同类精品
更多 icon
继续加载