多线程编程之线程间通信机制: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可以通知唤醒多个等待的线程

目录
相关文章
|
16天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
99 2
|
1月前
|
缓存 Java 调度
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
49 10
|
1月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
1月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
52 3
|
1月前
|
算法 调度 开发者
多线程编程核心:上下文切换深度解析
在多线程编程中,上下文切换是一个至关重要的概念,它直接影响到程序的性能和响应速度。本文将深入探讨上下文切换的含义、原因、影响以及如何优化,帮助你在工作和学习中更好地理解和应用多线程技术。
43 4
|
14天前
|
Java 调度
|
14天前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
2月前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
72 1
|
16天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
46 1
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
65 1