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

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

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();
        }
    }
}
复制代码


执行结果:


f300410014424da59d52a0affec53642~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


可以发现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();
        }
    }
}
复制代码


结果如下:


5dbca87ea0b34b0c8570ac5fceafe113~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


可以发现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();
        }
    }
}
复制代码


结果如下:


515088e7640d46e59be00126fdc939d2~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


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());
        }
    }
}
复制代码


结果如下:


81d46544f3f24051a120c9c82efef4c7~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


在使用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();
        }
    }
复制代码


结果如下:


0ce64866c58f49fc9a6178851057e060~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


前面提到了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);
    }
}
复制代码


结果如下:


66e60d742b564bf3acf78ae835969630~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


可以发现当线程呈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;
        }
    }
}
复制代码


结果如下:


97f05038f1f34a5dac88768f2ae85f21~tplv-k3u1fbpfcp-zoom-in-crop-mark_1304_0_0_0.webp.jpg


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


3 总结


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


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


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


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


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


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


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

目录
相关文章
|
2天前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
6天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
11天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
36 9
|
14天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
11天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
14天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
28 3
|
14天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
15天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
42 1
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
48 1
C++ 多线程之初识多线程
|
29天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3