Java多线程技术二:线程间通信——wait/notify机制

news2025/2/27 16:38:12

1 概述

        线程时操作系统中独立的个体,但这些个体如果不经过特殊的处理是不能成为一个整体的。线程间的通信就是使线程成为整体的比用方案之一,可以说,是线程间进行通信后系统之间的交互性会更强大,CPU利用率会得以大幅提高,同时程序员在处理的过程中可以有效把控与监督各线程任务。

2 不使用wait/notify机制进行通信的缺点

public class MyList {
    volatile private List list = new ArrayList<>();
    public void add(){
        list.add("jay chou");
    }
    public int size(){
        return list.size();
    }

}

  

public class ThreadA extends Thread{
    private MyList list;

    public ThreadA(MyList list) {
        this.list = list;
    }

    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                list.add();
                System.out.println("添加了" + (i + 1) +"个元素");
                Thread.sleep(1000);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class ThreadB extends Thread{
    private MyList list;

    public ThreadB(MyList list) {
        this.list = list;
    }

    @Override
    public void run(){
        try {
            while(true){
                if(list.size() == 5){
                    System.out.println(" == 5了,线程b要退出了");
                    throw new InterruptedException();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyList list = new MyList();
        ThreadA a = new ThreadA(list);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(list);
        b.setName("b");
        b.start();
    }
}

      

        虽然两个线程之间实现了通信,但还存在缺点:线程ThreadB不停地通过while语句轮询机制来检测某一个条件,这样会浪费CPU资源。如果轮询的时间间隔很短,更浪费CPU资源;如果轮询的时间很长,有可能会取不到想要得到的数据。

3 什么是wait/notify机制

         wait/notify(等待/通知)机制在生活中很常见,比如在就餐时就会出现,如下图:

        厨师和服务员在”菜品传递台“上交互,在这期间会想到几个问题:

        1、厨师做完一个菜的时间未知,所以厨师把菜品放到”菜品传递台“上的时间也未知。

        2、服务员取到菜的时间取决于厨师,所以服务器就有”wait“的状态。

        3、服务器如何能取到菜?这又要取决于厨师。厨师将菜放到”菜品传递台“上,其实就相当于一个notify,这时服务员才可以拿到菜并交给就餐者。

        4、这个过程中就出现了”wait/notify“机制。

        需要说明的是,前文中多个线程之间也可以实现通信,就是就是多个线程共同访问同一个变量。但那种通信机制却不是”等待/通知“,两个线程完全是主动操作同一个共享变量。 但那种通信机制却不失“等待/通知”,两个线程完全是主动操作同一个共享变量,在花费读取时间的基础上,读到的值并不确定是不是想要的。

4 wait/notify机制的原理

        注意:拥有相同锁的线程才可以实现wait/notify机制。所以下文都是假定操作同一个锁。

        wait()是Object类的方法,它的作用是使当前执行wait()方法的线程进行等待,在wait()所在的代码行处暂停执行,并释放锁,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。通过通知机制是某个线程继续执行wait()方法后面的代码时,对线程的选择是按照执行wait()方法的顺序确定的,并需要重新获得锁。如果调用wait()时没有持有适当的锁,则抛出IllegalMonitorStateException。它是RuntimeException的一个子类,因此不需要try-catch语句进行捕获异常。

        notify()也要在同步方法或同步块中调用,即在调用前线程必须要获得锁,如果调用notify()没有持有适当的锁,也会抛出IllegalMonitorStateException。该方法用来通知哪些可能等待该锁的其他线程,如果有多个线程等待,则按照执行wait()方法的顺序对呈等待状态的线程发出1次通知,并使那个线程重新获取锁。需要说明的是,执行notify()方法后,当前线程不会马上释放锁,呈等待状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步区域后,当前线程才会释放锁,而呈等待状态所在的线程才可以获取该对象锁。当第一个获得了该对象锁的等待线程运行完毕后,它会释放掉该对象锁。此时如果没有再次使用notify语句,那么其他等待状态的线程会因为没有得到通知而继续等待。

        wait和notify是Object类中的方法。总结来说:wait是线程暂停运行,而notify通知暂停的线程继续运行。

5 wait()的基本用法

        wait()的作用是使当前线程暂停运行,并释放锁。   

public class Test1 {
    public static void main(String[] args) {
        try {
            String s = new String("");
            s.wait();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

        出现异常的原因是没有“对象监视器”,也就是没有锁。

public class Test2 {
    public static void main(String[] args) {
        try {
            String lock = new String();
            System.out.println("synchronzied之前");
            synchronized (lock){
                System.out.println("进入synchronzied代码块");
                lock.wait();
                System.out.println("执行完wait()方法");
            }
            System.out.println("执行完synchronzied代码块的代码");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

 

        此时线程已经开始等待,但不能永远等待下去,否则程序就不会继续向下运行了。使用notify()方法可以是等待状态的线程继续运行。

6 实现wait/notify机制

public class MyThread1 extends Thread{
    private Object lock;

    public MyThread1(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run(){
        try {
            synchronized (lock){
                System.out.println("开始 等待时间 = " + Utils.data(System.currentTimeMillis()));
                lock.wait();
                System.out.println("结束 等待时间 = " + Utils.data(System.currentTimeMillis()));
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class MyThread2 extends Thread{
    private Object lock;

    public MyThread2(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run(){
        synchronized (lock){
            System.out.println("开始 通知时间 = " + Utils.data(System.currentTimeMillis()));
            lock.notify();
            System.out.println("结束 通知时间 = " + Utils.data(System.currentTimeMillis()));
        }

    }
}
public class Run1 {
    public static void main(String[] args) {
        try {
            Object object = new Object();
            MyThread1 t1 = new MyThread1(object);
            t1.start();
            Thread.sleep(3000);
            MyThread2 t2 = new MyThread2(object);
            t2.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}

        从运行结果来看,3秒后线程被通知唤醒。

7 使用wait/notify实现线程销毁

        使用wait/notify可以在某个特定条件下,销毁线程。

public class MyList {
    private static List list= new ArrayList<>();
    public static void add(){
        list.add("1");
    }

    public static int size(){
        return list.size();
    }
}

public class ThreadA extends Thread{
    private Object lock;

    public ThreadA(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run(){
        try {
            synchronized (lock){
                if(MyList.size() != 5){
                    System.out.println("开始等待 = " + Utils.data(System.currentTimeMillis()));
                    lock.wait();
                    System.out.println("结束等待 = " + Utils.data(System.currentTimeMillis()));
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class ThreadB extends Thread{
    private Object lock;

    public ThreadB(Object lock) {
        this.lock = lock;
    }
    @Override
    public void run(){
        try {
            synchronized (lock){
                for (int i = 0; i < 10; i++) {
                    MyList.add();
                    if(MyList.size() == 5){
                        lock.notify();
                        System.out.println("已发出通知");
                    }
                    System.out.println("添加了 " + (i+1));
                    Thread.sleep(1000);
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadA t1 = new ThreadA(lock);
        t1.start();
        Thread.sleep(50);
        ThreadB t2 = new ThreadB(lock);
        t2.start();

    }
}

        日志信息“结束等待”在最后输出,这也说明notify()方法执行后并不立即释放锁,这点后面会补充介绍。

        关键字synchronzied可以将任何一个Object作为锁来看待,而Java为每个Object都实现了wait()和notify()方法,它们必须用在被同步的Object的临界区内。通过调用wait()方法可以使处于临界区内的线程进入等待状态,同时释放被同步对象的锁,而notify操作可以唤醒一个由于调用了wait操作而处于wait状态中的线程,使其进入就绪状态,被重新唤醒的线程会试图重新获取临界区的控制权,也就是锁,并继续执行临界区内wait之后的代码。如果发出notify操作时没有处于wait状态中的线程,那么该命令就会被忽略。

        notify()方法按照执行wait方法的顺序唤醒等待同一个锁的“一个”线程,进入可运行状态。也就是notify()方法仅通知“一个”线程。而notifyAll()方法执行后,会按照执行 wait()方法的倒序依次唤醒全部的线程。

8 对业务代码进行凤封装

        前面的代码是在自定义的类中处理业务,而业务代码要尽量放在Service类中进行处理。

public class MyList {
    volatile private List list = new ArrayList<>();
    public void add(){
        list.add("q");
    }
    public int size(){
        return list.size();
    }
}

public class MyService {
    private Object lock = new Object();
    private MyList list = new MyList();
    
    public void watiMethod(){
        try {
            synchronized (lock){
                if(list.size() != 5){
                    System.out.println("开始等待 = " + Utils.data(System.currentTimeMillis()) + " " + Thread.currentThread().getName());
                    lock.wait();
                    System.out.println("结束等待 = " + Utils.data(System.currentTimeMillis()) + " " + Thread.currentThread().getName());
                    
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    
    public void notifyMethod(){
        try {
            synchronized (lock){
                System.out.println("开始通知 = " + Utils.data(System.currentTimeMillis()) + " " + Thread.currentThread().getName());
                for (int i = 0; i < 10; i++) {
                    list.add();
                    if(list.size() == 5){
                        lock.notify();
                        System.out.println("发出通知,wait后面的代码还没有立即执行,因为锁没有释放");
                    }
                    System.out.println("add次数 " + (i+1));
                    Thread.sleep(1000);
                }

                System.out.println("结束通知 = " + Utils.data(System.currentTimeMillis()) + " " + Thread.currentThread().getName());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.watiMethod();
    }
}
public class ThreadB extends Thread{
    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }

    @Override
    public void run(){
        service.notifyMethod();
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA t1 = new ThreadA(service);
        t1.start();
        Thread.sleep(3000);
        ThreadB t2 = new ThreadB(service);
        t2.start();
    }
}

9 线程状态的切换 

        前面介绍了与Thread有关的大部分API,这些API可以改变线程对象的状态。

        1)创建了一个新的线程对象后,再调用它的start方法,系统会为此线程分配CPU资源,处于可运行状态,是一个准备运行的阶段。如果线程抢占到CPU资源,此线程就处于运行状态。

        2)可运行状态和运行状态可互相切换,因为有可能线程运行一段时间后其他高优先级的线程抢占了CPU资源,这时此线程就从运行状态变成可运行状态。

        线程进入可运行状态可分为以下四种情况:

        1、调用sleep()方法后经过的时间超过了指定的休眠时间;

        2、线程成功获得了试图同步的监视器;

        3、线程正在等待某个通知,其他线程发出了通知;

        4、处于挂起状态的线程调用了resume方法。

        3)暂停状态结束后,线程进入可运行状态,等待系统重新分配资源。

            出现阻塞的情况可分为以下5种:

                1、线程调用sleep方法,主动放弃占用的处理器资源;

                2、线程调用了阻塞式I/O方法,在该方法返回前,该线程被阻塞;

                3、线程试图获得一个同步监视器,但该监视器正被其他线程所持有;

                4、线程等待某个通知;

                5、程序调用了suspend方法将该线程挂起。此方法容易导致死锁,应尽量避免使用。

        4)run方法运行结束后进入销毁阶段,整个线程执行完毕。

10 wait()方法导致锁立即释放

        wait()被执行后,锁会被立即释放,但执行完notify方法后,锁不会立即释放。

public class Service {
    public void testMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println("开始等待:");
                lock.wait();
                System.out.println("结束等待");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class ThreadA extends Thread{
    private Object lock;

    public ThreadA(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run(){
        Service service = new Service();
        service.testMethod(lock);
    }
}
public class ThreadB extends Thread{
    private Object lock;

    public ThreadB(Object lock) {
        this.lock = lock;
    }
    @Override
    public void  run(){
        Service service = new Service();
        service.testMethod(lock);
    }
}

public class Run1 {
    public static void main(String[] args) {
        Object locl = new Object();
        ThreadA t1 = new ThreadA(locl);
        t1.start();
        ThreadB t2 = new ThreadB(locl);
        t2.start();
    }
}

              

11 sleep()方法不释放锁

        如果将wait方法改成sleep方法,就成了同步效果。

public class Service {
    public void testMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println("开始等待:");
                Thread.sleep(4000);
                System.out.println("结束等待");
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

 

12 notify方法不立即释放锁

        还有一个结论要进行实验:方法notify被执行后,不立即释放锁。

public class MyService {
    private Object lock = new Object();
    public void testMethod(){
        try {
            synchronized (lock){
                System.out.println("开始等待时间 : " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());
                lock.wait();
                System.out.println("结束等待时间 : " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public void testNofity(){
        try {
            synchronized (lock){
                System.out.println("开始通知时间 : " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());
                lock.notify();
                Thread.sleep(4000);
                System.out.println("结束通知时间 : " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

     

public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }

    @Override
    public void run(){
        service.testMethod();
    }
}

     

public class ThreadB extends Thread{
    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }
    @Override
    public void  run(){
        service.testNofity();
    }
}

   

public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA t1 = new ThreadA(service);
        t1.start();
        Thread.sleep(500);
        ThreadB t2 = new ThreadB(service);
        t2.start();
    }
}

通过控制台打印的时间来分析,得到的结论:必须执行完notify方法所在的同步代码块后,才释放锁。

13 notify方法只通知一个线程

        每次调用notify方法时,只通知一个线程进行唤醒,唤醒的顺序按执行wait方法的正序。

public class MyService {
    private Object lock = new Object();

    public void waitMethod(){
        try {
            synchronized (lock){
                System.out.println("开始等待时间 = " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());
                lock.wait();
                System.out.println("结束等待时间 = " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public void notifyMethod(){
        synchronized (lock){
            System.out.println("开始唤醒时间 = " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());
            lock.notify();
            System.out.println("结束唤醒时间 = " + Utils.data(System.currentTimeMillis()) + Thread.currentThread().getName());
        }
    }
}
public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    @Override
    public void run(){
        service.waitMethod();
    }
}
public class ThreadB extends Thread{
    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }

    @Override
    public void run(){
        service.notifyMethod();
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        for (int i = 0; i < 10; i++) {
            ThreadA t1 = new ThreadA(service);
            t1.start();
        }
        Thread.sleep(1000);
        ThreadB t1 = new ThreadB(service);
        t1.start();
        Thread.sleep(500);
        ThreadB t2 = new ThreadB(service);
        t2.start();
        Thread.sleep(500);
        ThreadB t3 = new ThreadB(service);
        t3.start();
        Thread.sleep(500);
        ThreadB t4 = new ThreadB(service);
        t4.start();
        Thread.sleep(500);
        ThreadB t5 = new ThreadB(service);
        t5.start();
    }

        通过以上几个实验,得出以下三个结论:
        1、执行完notify方法后,按照执行wait的顺序唤醒其他线程。notify所在的同步到代码块执行完才会释放对象的锁,其他线程继续执行wait之后的代码。

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

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

14 notifyAll方法通知所有线程

        前面示例中通过多次调用notify方法来实现5个线程被唤醒,当并不能保证实际系统中仅有5个线程,就是notify方法的调用次数小于线程对象的数量,那么会出现部分线程对象没有被唤醒的情况。为了唤醒全部线程,可以使用notifyAll方法。 

       注意,notifyAll方法会按照执行wait方法的倒序依次对其他线程进行唤醒。对上一节的MyService.java进行修改,把notifyMethod()方法中的notify()改成notifyAll(),然后再新建Run2.java类。

public class Run2 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        for (int i = 0; i < 10; i++) {
            ThreadA t1 = new ThreadA(service);
            t1.start();
        }
        Thread.sleep(1000);
        ThreadB t2 = new ThreadB(service);
        t2.start();
    }
}

        通过运行结果看到,唤醒的顺序是调用wait()方法的倒序。 

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

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

相关文章

前端开发环境与真实环境的接口联通的那些最佳实践

1. 背景 前端开发的产物通常是 app.js 、app.css &#xff0c;然后将这些资源放在真实环境域名下进行工作的。 但前端的开发环境通常是本地的 http://localhost:xxx&#xff0c;业务域名可能是 https://xxx.abc.com&#xff0c;两者不在一个域名下在调用接口或者调试时会非常不…

架构图是什么,怎么做?

架构图是一种用来描述系统或软件的结构和组成的图形表示。它展示了系统中各个组件之间的关系、交互和功能。通过绘制架构图&#xff0c;可以更好地理解和沟通系统的设计和实现。 绘制架构图的软件 目前市场上有许多用于绘制架构图的软件工具&#xff0c;下面简单…

【STM32】STM32学习笔记-课程简介(1)

00. 目录 文章目录 00. 目录01. 课程简介02. 硬件设备03. 软件工具04. 硬件套件4.1 面包板和跳线/飞线4.2 杜邦线和STM32最小系统板4.3 STLINK和OLED显示屏4.4 LED和按键4.5 电位器和蜂鸣器4.6 传感器和旋转编码器4.7 USB转串口和MPU60504.8 Flash闪存和电机模块4.9 SG90舵机 0…

【全栈开发】使用NestJS、Angular和Prisma 打造全栈Typescript开发

在开发Angular应用程序时&#xff0c;我非常喜欢Typescript。使用NestJS&#xff0c;您可以以与Angular非常相似的方式编写后端。 我偶然发现了这个库&#xff0c;发现它非常有趣&#xff0c;所以我想设置一个简单的测试项目。一般来说&#xff0c;我主要使用SQL数据库&#x…

FreeRTOS入门--任务

目录 一、什么是任务 二、创建任务---xTaskCreate函数 三、任务的删除 四、任务优先级 1.阻塞状态(Blocked) 2.暂停状态(Suspended) 3.就绪状态(Ready) 五、Delay 六、调度算法 一、什么是任务 在FreeRTOS中&#xff0c;任务就是一个函数&#xff0c;原型如下&#xff…

算法通关村第四关—栈的经典算法问题(白银)

emsp;emsp;栈的经典算法问题 一、括号匹配问题 emsp;首先看题目要求&#xff0c;LeetCode20.给定一个只包括’(‘&#xff0c;)’&#xff0c;‘{&#xff0c;’&#xff0c;[&#xff0c;]的字符串s&#xff0c;,判断字符串是否有效。有效字符串需满足&#xff1a; 1.左括号…

fl studio21.2最新汉化中文完整版网盘下载

fl studio 21中文版是Image-Line公司继20版本之后更新的水果音乐制作软件&#xff0c;很多用户不太理解&#xff0c;为什么新版本不叫fl studio 21或fl studio2024&#xff0c;非得直接跳到21.2版本&#xff0c;其实该版本是为了纪念该公司22周年&#xff0c;所以该版本也是推出…

【嵌入式-51单片机】常见位运算和数据类型以及sbit使用

51单片机中 数据类型如下&#xff1a; 位运算符如下&#xff1a; 按位左移<<&#xff1a;低位补零&#xff0c;高位移出 按位右移>>&#xff1a;高位补零&#xff0c;低位移出 按位与&&#xff1a;对应位上的值必须同时为1才为1&#xff0c;可以用来对指定位…

交换综合实验

目录 一、实验拓扑 二、实验要求 三、实验步骤 1、链路聚合&#xff08;配置Eth-trunk&#xff09; 2、配置vlan&#xff08;创建划分vlan&#xff0c;配置trunk干道&#xff09; 3、MSTP配置 4、VRRP配置 5、DHCP配置 6、vlan互通 7、NAT配置&#xff08;做ACL&#…

解决Linux的端口占用报错问题

文章目录 1 Linux报错2 解决方式 1 Linux报错 Port 6006 is in use. If a gradio.Blocks is running on the port, you can close() it or gradio.close_all(). 想起之前运行Gradio 6006&#xff0c;端口被占用 2 解决方式 输入 netstat -tpl查看当前一些端口号的占用号&a…

Nginx实战教程二

一.介绍 本文介绍SPRINGBOOTVUE项目配置API服务器的两种情况 NGINX 配置VUE项目 二.vue项目和后端api接口不在同一台服务器 如果打包好的vue项目应用(dist) 和后端 api 接口没有运行在同一个主机上 此时需要在开发环境下将 API 请求代理到 API 所在服务器。通过配置 vue.confi…

【开源存储】glusterfs分布式文件系统部署实践

文章目录 一、前言1、介绍说明2、术语说明3、冗余模式3.1、复制卷&#xff08;Replication&#xff09;3.2、纠删卷&#xff08;Erasure Code&#xff09; 二、部署说明1、软件安装2、集群部署2.1、前置准备2.2、部署过程a、添加节点b、配置存储c、创建glusterfs卷d、客户端挂载…

浅谈安科瑞AISD300系列智能三相安全配电装置的设计与应用-安科瑞 蒋静

1 概述 AISD300系列三相智能安全配电装置是安科瑞专为低压配电侧开发的一款智能安全配电产品&#xff0c;本产品主要针对低压配电系统人身触电、线路老化、短路、漏电等原因引起电气安全问题而设计。 产品主要应用于学校、加油站、医院、银行、疗养院、康复中心、敬老院、酒店…

关于微信公众号授权的几件事

背景 项目需要使用微信公众号发消息&#xff0c;然后就来接入这个微信授权啦&#xff0c;微信公众号发消息前提是还需要用户先关注公众号~ 微信授权是有点恶心的&#xff0c;真的真的需要先配置好环境&#xff0c;开发的话目前是可以使用测试号申请公众号使用测试号的appid~ …

N-135基于springboot,vue高校图书馆管理系统

开发工具&#xff1a;IDEA 服务器&#xff1a;Tomcat9.0&#xff0c; jdk1.8 项目构建&#xff1a;maven 数据库&#xff1a;mysql5.7 系统分前后台&#xff0c;项目采用前后端分离 前端技术&#xff1a;vueelementUI 服务端技术&#xff1a;springbootmybatisredis 本项…

微机原理——定时器学习2应用与设计

目录 简要说明 用户扩展的定时计数器应用举例 1 8254作测量脉冲宽度 2 8254作定时 3 8254作分频 4 8254同时用作计数与定时 硬件设计 ​编辑软件设计 微机系统中定时计数器应用举例 5 计时器设计 硬件设计 软件设计 6 发生器设计 硬件设计 软件设计 简要说明 定…

鸿蒙工具DevEco Studio调试Build task failed. Open the Run window to view details.

DevEco Studio 预览代码时候出现的问题 1.进入设置 2.打开设置&#xff0c;构建&#xff0c;执行&#xff0c;部署下面的Hvigor&#xff0c; 把构建守护进程关掉就行。 然后重启启动一下就好了

五子棋AI算法自动测试方法

先前发了几篇五子棋游戏程序设计的博文&#xff0c;设计了游戏程序&#xff0c;也设计了AI智能奕棋的算法&#xff0c;运行程序检测算法的可行性&#xff0c;完成人机模式游戏功能的设置。 本文主要介绍自动测试算法的方法。 AI智能奕棋的算法testAIq( )&#xff0c;主要是检测…

【Android】Android Framework系列--Launcher3桌面图标加载流程

Launcher3桌面加载流程 Android Launcher3(简称Launcher&#xff09;启动后会加载桌面。基于Android12代码&#xff0c;分析一下桌面加载的流程。 一些相关的概念&#xff1a; WorkSpace&#xff1a;桌面。在桌面上可以添加快捷方式、Hoseat或Dock&#xff08;就是手机或者车…

k8s(三): 基本概念-ReplicaSet与Deployment

PeplicaSet ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合&#xff0c;通常用来保证给定数量的、完全相同的 Pod 的可用性。 最佳实践 Deployment 是一个可以拥有 ReplicaSet 并使用声明式方式在服务器端完成对 Pod 滚动更新的对象。 尽管 Rep…