JUC并发编程——wait-notify

news2024/11/15 15:59:24

目录

    • 一、wait / notify
      • 1.1 wait / notify 原理
      • 1.2 wait / notify API介绍
    • 二、wait VS sleep
    • 三、wait / notify —代码改进

一、wait / notify

1.1 wait / notify 原理

在这里插入图片描述

● Owner线程发现条件不满足,调用wait( )方法即可进入WaitSet变为 WAITING状态

● BLOCKED 和 WAITING的线程都处于阻塞状态,不占用CPU时间片(相同点)

● BLOCKED 线程会在 Owner线程释放锁时唤醒

● WAITING 线程会在 Owner线程调用 notifynotifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入EntryList重新竞争

1.2 wait / notify API介绍

obj.wait() 让进入 object 监视器的线程到 waitSet 等待
obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒
obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

它们都是线程之间进行协作的手段,都属于 Object 对象的方法。无论是wait还是notify 必须获得此对象的锁,才能调用这几个方法

示例
在这里插入图片描述

正常运行:

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.Test18")
public class Test18 {
    static final Object lock = new Object();
    public static void main(String[] args) {

        synchronized (lock) {
            try {
            /* 需先获取对象锁,成为Owner后才能调wait();
            这时才能进入lock所关联的Monitor对象中的WaitSet中WAITING
             */
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

notify():挑一个唤醒

import lombok.extern.slf4j.Slf4j;

import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    final static Object obj = new Object();

    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t1").start();

        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t2").start();

        // 主线程两秒后执行
        sleep(2);
        log.debug("唤醒 obj 上其它线程");
        // 进入同一个对象中的Monitor
        synchronized (obj) {
            // 唤醒obj上一个线程(挑一个线程唤醒)
           obj.notify();
           

            //     obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}

运行结果:

在这里插入图片描述

notifyAll():全部唤醒

在这里插入图片描述

wait() 方法会释放对象的锁,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify 为止(wait(0)也会无限制等待)

wait(long n) 有时限的等待, 到 n 毫秒后结束等待,或是被 notify

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    final static Object obj = new Object();

    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    // 让线程t1在obj上等待1s
                    obj.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t1").start();
     }
  }      

运行结果:即使未唤醒也会结束

在这里插入图片描述

若在等待期间被其他线程唤醒,则会恢复,不会等够时间才才向下运行

import lombok.extern.slf4j.Slf4j;

import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    final static Object obj = new Object();

    public static void main(String[] args) {

        new Thread(() -> {
            synchronized (obj) {
                log.debug("执行....");
                try {
                    // 让线程t1在obj上等待1s
                    obj.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t1").start();


        // 主线程0.5秒后执行
        sleep(0.5);
        log.debug("唤醒 obj 上其它线程");
        // 进入同一个对象中的Monitor
        synchronized (obj) {
            obj.notifyAll();
        }
    }
}

运行结果:

在这里插入图片描述

二、wait VS sleep

sleep(long n) 和 wait(long n) 的区别

  1. sleep 是 Thread 的静态方法,而 wait 是 Object 的方法(所有的对象都有的方法)
  2. sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
  3. sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
  4. 它们状态都为 TIMED_WAITING(有时限的等待)

sleep(0)触发操作系统立刻重新进行一次CPU的竞争。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权

tip作为锁的对象使用final修饰,final意味引用不可变(若引用发生变化,synchronized锁住的为不同对象)

sleep演示
在这里插入图片描述

wait演示:(1s后主线程便成功获得锁)

在这里插入图片描述

三、wait / notify —代码改进

问题背景:模拟线程使用共享的room来达到线程安全

//  共享变量(线程安全的操作)
static final Object room = new Object();
static boolean hasCigarette = false;    // 是否有烟
static boolean hasTakeout = false;      // 外卖是否送到

思考下面的解决方案是否较好,为什么?

new Thread(() -> {
    synchronized (room) {
      log.debug("有烟没?[{}]", hasCigarette);
      if (!hasCigarette) {
           log.debug("没烟,先歇会!");
           sleep(2);
      }
      log.debug("有烟没?[{}]", hasCigarette);
      if (hasCigarette) {
            log.debug("可以开始干活了");
      }
   }
}, "小南").start();

// 其他线程
for (int i = 0; i < 5; i++) {
    new Thread(() -> {
      synchronized (room) {
          log.debug("可以开始干活了");
      }
    }, "其它人").start();
 }
 
 // 主线程等待1秒
sleep(1);
new Thread(() -> {
    // 这里能不能加 synchronized (room)?
    hasCigarette = true;
   log.debug("烟到了噢!");
}, "送烟的").start();

观察7个线程的工作流程:
在这里插入图片描述
出现的问题(缺点):

  1. 其它干活的线程,都要一直阻塞,效率太低
  2. 小南线程必须睡足 2s 后才能醒来,就算烟提前送到,也无法立刻醒来
  3. 加了 synchronized (room) 后,就好比小南在里面反锁了门睡觉,烟根本没法送进门,main 没synchronized 就好像 main 线程是翻窗户进来的

● 解决方法:使用 wait - notify 机制

import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("可以开始干活了");
                }
            }, "其它人").start();
        }

        sleep(1);
        // 主线程等待1s后启动睡眠线程
        new Thread(() -> {
             synchronized (room) {
                 hasCigarette = true;
                 log.debug("烟到了噢!");
                 // 唤醒正在睡眠的线程
                room.notify();
             }
        }, "送烟的").start();
    }
}

运行结果:(并发效率得到大大提升)
在这里插入图片描述
深度思考

如果有其他线程也在等待条件呢?(送烟线程会不会错误唤醒其他线程)

import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {
    static final Object room = new Object();
    static boolean hasCigarette = false; // 有没有烟
    static boolean hasTakeout = false;   // 外卖是否送到

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();


        // 小女线程等待外卖
        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外卖送到了没?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("没外卖,先歇会");
                }
                try {
                    room.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("外卖送到了没?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以干活了");
                } else {
                    log.debug("没干成活");
                }
            }
        }, "小女").start();

        sleep(1);
        // 主线程等待1s后启动睡眠线程
        new Thread(() -> {
            synchronized (room) {
                hasCigarette = true;
                log.debug("外卖到了噢!");
                room.notify();     // 调用notify()时,只能在room中等待的线程中随机挑一个唤醒
            }
        }, "送外卖的").start();
    }
}

运行结果:
在这里插入图片描述

出现的问题(缺点):notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为虚假唤醒

● 解决方法:使用 notifyAll将所有线程唤醒

运行结果:
在这里插入图片描述

出现的问题(缺点):用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了

● 解决方法:使用 while + wait,当条件不成立,再次 wait

import lombok.extern.slf4j.Slf4j;
import static cn.itcast.n2.util.Sleeper.sleep;

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("有烟没?[{}]", hasCigarette);
                // 线程还可以进入下一轮的等待
                while (!hasCigarette) {
                    log.debug("没烟,先歇会!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("可以开始干活了");
                }else {
                    log.debug("没干成活......");
                }
            }
        }, "小南").start();


        // 小女线程等待外卖
        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("外卖送到了没?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("没外卖,先歇会");
                }
                try {
                    room.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("外卖送到了没?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("可以干活了");
                } else {
                    log.debug("没干成活");
                }
            }
        }, "小女").start();

        sleep(1);
        // 主线程等待1s后启动睡眠线程
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("外卖到了噢!");
                room.notifyAll();     // 调用notifyAll()时,将所有在room中等待的线程全部唤醒
            }
        }, "送外卖的").start();
    }
}

运行结果:
在这里插入图片描述

总结

● 正确使用wait-notify的格式:

synchronized (lock) {
   while(条件不成立){
       lock.wait();
   }
  // 条件成立,继续向下运行
}

// 另一个线程
synchronized (lock) {
    lock.notifyAll();
  }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/391253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI的简单介绍

什么是AI&#xff1f; AI 是 Artificial Intelligent 的缩写&#xff0c;是我们通常意义上说的人工智能。 简单来说就是让机器能够模拟人类的思维能力&#xff0c;让它能够像人一样感知、思考甚至决策。 为什么要开发AI&#xff1f; 因为在过去&#xff0c;都是我们学习机器…

408 计算机基础复试笔记 —— 更新中

计算机组成原理 计算机系统概述 问题一、冯诺依曼机基本思想 存储程序&#xff1a;程序和数据都存储在同一个内存中&#xff0c;计算机可以根据指令集执行存储在内存中的程序。这使得程序具有高度灵活性和可重用性。指令流水线&#xff1a;将指令分成若干阶段&#xff0c;每…

opencv学习(一)图像的基本操作

数据的读取cv2.IMREAD_COLOR:彩色图像cv2.IMREAD_GRAYSCALE:灰度图像import cv2 img cv2.imread(E:/opencv/open-cv/2-7/cat.jpg,1)cv2.imshow("img", img) cv2.waitKey(0) cv2.destroyAllWindows() cv2.imread()读取图片&#xff0c;当括号里面是1时&#xff…

华为OD机试题,用 Java 解【水仙花数】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

抓包技术(浏览器APP小程序PC应用)

P1 抓包工具 01. Fidder 首先第一个Fiddler它的优势&#xff0c;独立运行&#xff0c;第二个支持移动设备&#xff08;是否能抓移动APP的包&#xff0c;&#xff09;在这一块的话wireshark、httpwatch就不支持&#xff0c;因此在这一块就可以排除掉前连个&#xff0c;因为我们…

SpringCloudGateway--基于redis实现令牌桶算法

目录 一、限流算法 1、计数器算法 2、漏桶算法 3、令牌桶算法 二、Gateway中的限流 一、限流算法 1、计数器算法 计数器算法是指从第一个请求开始&#xff0c;每多一个请求就加1&#xff0c;假设设置每秒限流100&#xff0c;当在一秒钟前500ms已经达到100&#xff0c;后面的5…

华为云计算HCIA学习笔记-第1章 云计算基础概念

1.1 云计算课程安排说明 &#xff08;IA-虚拟化-FC / IP-Linux OpenStack 桌面云/IE-备份容灾迁移&#xff09; 1.2 为什么云计算IA讲虚拟化&#xff1f; 提前告知学员&#xff0c;为什么IA课程要重点讲解虚拟化&#xff1f;云计算基于OpenStack&#xff0c;其底层虚拟化技术…

赞!中原银行|古风金融产品体验运营数字驾驶舱大屏!

本期「V友故事」&#xff1a;中原银行设计师作品——古风金融产品体验运营大屏 金融行业数字化转型是近年来提出的一个关键词&#xff0c;它不仅在改变金融服务的结构、形式和方式&#xff0c;而且也在影响中国金融行业的扩张规模&#xff0c;基于此行业趋势&#xff0c;金融从…

加密图像的脆弱水印及应用

原文题目&#xff1a;《A self-embedding secure fragile watermarking scheme with high quality recovery》 学习笔记&#xff1a; 应用场景 为了确保图像在传输过程中不被损坏&#xff0c;在将原始图像发送到云端之前&#xff0c;将用于篡改检测和恢复的水印嵌入到原始图像…

docker逃逸复现--pid=host模式下的逃逸

漏洞原理当docker以--pidhost模式启动时&#xff0c;你可以通过在容器进程中注入一些shellcode进行逃逸。相当于给了docker Linux中的CAP_SYS_PTRACE权限--pidhost&#xff1a;意味着宿主机与容器公享一套pid&#xff0c;如此做容器就可以访问并跟踪宿主机的进程Linux中的CAP_S…

【微信小程序】-- 页面导航 -- 导航传参(二十四)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &…

MyBatis框架快速入门 - 基本使用

文章目录MyBatisMyBatis基本介绍MyBaits快速入门Mapper代理开发MyBatis配置文件MyBatis MyBatis基本介绍 什么是MyBatis? MyBatis 是一款优秀的持久层框架&#xff0c;用于简化 JDBC 开发 MyBatis 本是 Apache 的一个开源项目iBatis, 2010年这个项目由apache software found…

FPGA实现模拟视频BT656解码 TW2867四路PAL采集拼接显示 提供工程源码和技术支持

目录1、前言2、模拟视频概述3、模拟视频颜色空间4、逐行与隔行5、BT656数据与解码BT656数据格式BT656数据解码6、TW2867芯片解读与配置TW2867芯片解读TW2867芯片配置TW2867时序分析7、设计思路与框架8、vivado工程详解9、上板调试验证10、福利&#xff1a;工程代码的获取1、前言…

【Spring6】| GoF之工厂模式

目录 一&#xff1a;GoF之工厂模式 1. 工厂模式的三种形态 2. 简单工厂模式 3. 工厂方法模式 4. 抽象工厂模式&#xff08;了解&#xff09; 一&#xff1a;GoF之工厂模式 &#xff08;1&#xff09;GoF&#xff08;Gang of Four&#xff09;&#xff0c;中文名——四人组…

前端开发总结的一些技巧和实用方法(2)

本文主要介绍一些JS中用到的小技巧和实用方法&#xff0c;可以在日常Coding中提升幸福度&#xff0c;也可以通过一些小细节来增加代码可读性&#xff0c;让代码看起来更加优雅&#xff0c;后续将不断更新1.数组 map 的方法 (不使用Array.Map) Array.from 还可以接受第二个参数…

一文讲解JDK自带监控工具查看 JVM 情况

在一文带你了解阿里的开源Java诊断工具 &#xff1a;Arthas_出世&入世的博客-CSDN博客这篇文章中介绍了Arthas的强大功能&#xff0c;但是有些生成环境没有安装&#xff0c;所以还是需要会使用JDK 自带监控JVM的工具。 常用的JDK 自带监控工具如下&#xff1a; jps&#x…

CDH大数据平台入门篇之搭建与部署

一、CDH介绍 1.CDH 是一个强大的商业版数据中心管理工具 提供了各种能够快速稳定运行的数据计算框架&#xff0c;如Spark&#xff1b; 使用Apache Impala做为对HDFS、HBase的高性能SQL查询引擎&#xff1b; 使用Hive数据仓库工具帮助用户分析数据&#xff1b; 提供CM安装HBas…

真正的IT技术男是什么样的?

我们经常会听到很多对IT男士的调侃称呼&#xff0c;“屌丝”、“宅男”&#xff0c;会逗的大家捧腹大笑。但是&#xff0c;大家要不要以为称呼IT男是“屌丝”、“宅男”&#xff0c;就当真以为他们是这样了。今天&#xff0c;青鸟学姐就带大家一起来了解一下&#xff0c;真正的…

代码还原小试牛刀(一):魔改的MD5

一、目标 2023年了&#xff0c;MD5已经是最基础的签名算法了&#xff0c;但如果你还只是对输入做了简单的MD5&#xff0c;肯定会被同行们嘲笑。加点盐&#xff08;salt&#xff09;是一种基本的提升&#xff0c;但在这个就业形势严峻的时代&#xff0c;仅仅加盐肯定不够了。 …

原腾讯QQ空间负责人,T13专家,黄希彤被爆近期被裁员,裁员原因令人唏嘘。。...

点击上方“码农突围”&#xff0c;马上关注这里是码农充电第一站&#xff0c;回复“666”&#xff0c;获取一份专属大礼包真爱&#xff0c;请设置“星标”或点个“在看这是【码农突围】的第 431 篇原创分享作者 l 突围的鱼来源 l 码农突围&#xff08;ID&#xff1a;smartyuge&…