线程通信之wait和notify

本文深入探讨线程通信机制,重点讲解wait和notify方法在多线程环境中的应用,包括其工作原理、使用场景及注意事项,同时对比分析了wait与sleep、yield等方法的区别。

layout: post title: "线程通信之wait和notify" subtitle: " "Object.wait()/notify()"" date: 2018-10-07 08:00:00 author: "青乡" header-img: "img/post-bg-2015.jpg" catalog: true tags: - multi thread

前言

线程之间怎么通信?
线程之间本来就是共享数据的,所以天然地就可以互通数据。而且多线程主要需要解决的问题也是如何确保多线程对共享数据的访问。


怎么实现通信?
其中有一种方式和应用场景就是需要使用wait/notify来控制线程执行的顺序,以及对共享数据的访问。

注:jdk也不推荐使用wait/notify!


1.每个对象有一个监视器。
2.每个对象还有一个等待线程集合。线程集合只能通过锁对象.wait/notify来操作。


进程怎么通信?
1.套接字
2.消息中间件
3.远程服务

2个不同的程序,可以实现数据互通!

作用

可以线程通信的问题。线程通信需要处理同步问题,怎么处理?有2种方法:
1.wait/notify
2.同步机制

怎么用

1.当前线程必须获取/拥有锁对象
哪个线程要调用锁对象的wait/notify,它必须要先获取锁对象。

线程对象调用锁对象的wait/notify之前,必须确保当前线程拥有锁对象。 所以,这就导致必须在同步代码块里调用锁对象的wait/notify。因为只有在同步代码块里调用锁对象的wait/notify,当前线程才会拥有锁对象,也才可能调用锁对象的wait/notify。

2.同一个对象
锁对象和锁对象.wait/notify,必须是同一个对象。否则会报错-非法监视器状态异常。

3.锁对象.wait/notify,必须在同步代码块/方法里
因为锁对象/监视器 和 锁对象.wait/notify 里的锁对象,必须是同一个对象。即2里说的。

//调用wait
synchronized (obj) { //当前线程必须获取锁对象 
         while (<condition does not hold>)
             obj.wait(timeout); //调用wait/notify的对象和锁对象是同一个对象 //调用wait/notify的代码必须在同步代码块里
         
         ... // Perform action appropriate to condition //读线程:读共享数据
     }

//调用notify
synchronized(对象){
    改变条件  //写线程:写共享数据
     对象.notifyAll();  //激活读线程
}
复制代码

wait方法

wait()等同于wait(0)。

参数为0,表示无限等待,即一直等待下去,直到被另外一个线程通知。

what is the difference between wait and sleep、yield?

共同点
都是停止当前线程执行。

不同点
1.是否放弃锁 wait //线程A会放弃锁。什么时候恢复?1.在指定时间到之前,有另外一个线程B修改数据之后即完成任务之后调用notify,线程A重新获取锁重新执行。2.一直没有别的线程调用notify,时间到,线程A继续获取锁继续执行。

sleep //线程A不会放弃锁。yield也是,只是交出CPU。在是否放弃锁方面,sleep和yield是一样的,都不放弃锁,而是只放弃CPU。

2.停止时间是否确定 sleep的停止时间是确定的,就是参数指定的时间。什么时候恢复?时间到了就恢复。

yield的停止时间是不确定的,它只是交出CPU,正在等待执行的线程集合里取一个同样优先级的线程来执行。也有可能是再次取的自己。什么时候恢复?时间不确定,依赖CPU和线程调度器。

javarevisited.blogspot.com/2011/12/dif… www.jianshu.com/p/25e959037…

wait和join的区别

1.锁对象.wait/notify
2.线程对象.join——》this.wait——》锁对象是线程对象
作用?让当前主线程等待,直到对象锁线程1( 指的是主线程调用线程1.join() )执行完毕。接着启动线程2。这样就确保了线程1和线程2按顺序执行。

www.importnew.com/14958.html www.java67.com/2017/11/dif…

线程执行完了,意味着什么?

public final synchronized void join(long millis)

    throws InterruptedException {

        long base = System.currentTimeMillis();

        long now = 0;



        if (millis < 0) {

            throw new IllegalArgumentException("timeout value is negative");

        }



        if (millis == 0) {

            while (isAlive()) { //对象锁线程1执行完毕,接着执行线程2——这是怎么实现的?看这里代码知道,线程1执行完毕,线程死亡,循环结束,线程1.join()方法执行完毕,继续执行主线程里的代码,即启动线程2。 //这里的问题是主线程在线程1对象锁上等待,而且是无线等待,除非有另外一个线程调用线程1.notify(),否则主线程应该还是等待。线程死了,join()方法执行完毕,这个可以理解——但是为什么线程1死了,join()执行完毕了,为什么主线程重新开始执行了?没有另一个线程去调用线程1.notify(),主线程怎么会活过来呢?线程1退出的时候即exit()的时候会notifyAll()。https://juejin.im/post/5b3054c66fb9a00e4d53ef75 //还有一个问题,调1次wait()不就行了吗?为什么要循环调用?因为调1次也是让主线程等待,调n次还是让主线程等待,所以循环调用的作用是什么?防止虚假唤醒,看API-Object.wait()。至于什么是虚假唤醒,还需要再看维基百科。

                wait(0);

            }

        } else {

            while (isAlive()) {

                long delay = millis - now;

                if (delay <= 0) {

                    break;

                }

                wait(delay);

                now = System.currentTimeMillis() - base;

            }

        }

    }


复制代码

juejin.im/post/5b3054…

docs.oracle.com/javase/7/do…

线程通信的各种场景分析

wingjay.com/2017/04/09/…

工作应用

支付-微服务
很多服务都不是web程序,而是java程序,怎么启动?当然是main方法启动。但是这个程序启动之后是不能关闭的,它要一直提供服务。怎么办?while(true)循环。

基于while(true)循环,改善为while(true)的情况下,还要让当前主线程wait,目的是不让程序关闭,一直向外提供服务,最重要的是,让程序wait,避免了循环执行无谓的代码去消耗和占用计算机的资源——说白了,就是不让CPU执行while(true)里的的代码,因为这是纯粹的浪费。

代码

package gzh.spring;


/*    */ import java.text.SimpleDateFormat;
/*    */ import java.util.Date;
/*    */ import org.apache.commons.logging.Log;
/*    */ import org.apache.commons.logging.LogFactory;
/*    */ 
/*    */ 
/*    */ public class Main
/*    */ {
/* 12 */   private static Log log = LogFactory.getLog(Main.class);
/*    */   
/* 14 */   private static volatile boolean running = true;
/*    */   
/*    */   public static void main(String[] args) {
/* 17 */     Runtime.getRuntime().addShutdownHook(new Thread() {
/*    */       public void run() {
/*    */         try {
/* 20 */           AppContext.stop();
/* 21 */           Main.log.info(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Main server stopped!");
/*    */         } catch (Throwable t) {
/* 23 */           Main.log.error("Main stop error:" + t);
/*    */         }
/* 25 */         synchronized (Main.class) {
///* 26 */           Main.access$102(false);
/* 27 */           Main.class.notify();
/*    */         }
/*    */       }
/*    */     });
/*    */     try
/*    */     {
/* 33 */       AppContext.start();
/*    */     } catch (RuntimeException e) {
/* 35 */       log.error(e.getMessage(), e);
/* 36 */       throw e;
/*    */     }
/*    */     
/* 39 */     log.info(new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss]").format(new Date()) + " Main server started!");
/*    */     
/* 41 */     synchronized (Main.class) {
/* 42 */       while (running) {
/*    */         try {
/* 44 */           Main.class.wait(); //让主线程wait
/*    */         }
/*    */         catch (Throwable e) {}
/*    */       }
/*    */     }
/*    */   }
/*    */ }

复制代码

参考

www.cnblogs.com/stateis0/p/…

docs.oracle.com/javase/tuto…

www.kancloud.cn/digest/java…

转载于:https://juejin.im/post/5bf2b6ae6fb9a049e307b3c6

Java线程编程中,`wait()` `notify()` 是用于实现线程通信的核心机制之一。它们允许线程在特定条件下等待或被唤醒,从而实现协调执行,避免资源竞争不必要的CPU消耗。 ### 线程通信的基本原理 `wait()` 方法使当前线程进入等待状态,并释放对象锁,直到其他线程调用 `notify()` 或 `notifyAll()` 方法来唤醒它。`notify()` 方法则用于唤醒在该对象上等待的单个线程(`notifyAll()` 会唤醒所有等待线程),但它不会立即释放锁,只有在当前线程退出同步块或方法后,被唤醒的线程才有机会重新获取锁并继续执行。 这两个方法必须与 `synchronized` 关键字一起使用,确保线程在调用 `wait()` 或 `notify()` 时已经获得了对象锁。否则会抛出 `IllegalMonitorStateException` 异常。 ### 示例代码 以下是一个使用 `wait()` `notify()` 实现的简单线程通信示例: ```java public class WaitNotifyExample { private final Object lock = new Object(); private boolean flag = false; public void waitForFlag() { synchronized (lock) { while (!flag) { try { System.out.println("Thread is waiting for flag to be true"); lock.wait(); // 释放锁并等待 } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } System.out.println("Flag is true now, proceeding"); } } public void setFlagTrue() { synchronized (lock) { flag = true; System.out.println("Flag set to true, notifying waiting thread"); lock.notify(); // 唤醒等待的线程 } } public static void main(String[] args) { WaitNotifyExample example = new WaitNotifyExample(); Thread t1 = new Thread(example::waitForFlag); Thread t2 = new Thread(example::setFlagTrue); t1.start(); try { Thread.sleep(1000); // 模拟延迟 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } t2.start(); } } ``` ### 执行流程说明 1. **线程 t1** 调用 `waitForFlag()`,进入同步块并检查 `flag` 是否为 `false`。由于初始为 `false`,它调用 `wait()`,释放锁并进入等待状态。 2. **线程 t2** 调用 `setFlagTrue()`,将 `flag` 设置为 `true`,然后调用 `notify()` 唤醒在 `lock` 上等待的线程。 3. **线程 t1** 被唤醒后重新竞争锁,再次检查 `flag`,此时为 `true`,继续执行后续逻辑。 ### 注意事项 - **必须在同步上下文中调用**:`wait()` `notify()` 必须在 `synchronized` 方法或代码块中调用,以确保线程持有对象锁。 - **使用循环判断条件**:为了避免虚假唤醒(spurious wakeups),通常将 `wait()` 放在 `while` 循环中,确保条件真正满足后再继续执行。 - **避免死锁**:确保至少有一个线程可以改变条件并调用 `notify()` 或 `notifyAll()`,否则可能导致所有线程都处于等待状态。 - **异常处理**:调用 `wait()` `notify()` 可能抛出 `InterruptedException`,应妥善处理中断信号,避免线程挂起。 ### 优势与应用场景 相比轮询机制,`wait/notify` 能显著降低CPU资源的浪费。适用于多个线程之间需要根据某些状态进行协调的场景,例如生产者-消费者模型、任务调度、状态同步等。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值