Java多线程编程—wait/notify机制

news2024/11/14 11:02:00

文章目录

    • 1. 不使用wait/notify机制通信的缺点
    • 2. 什么是wait/notify机制
    • 3. wait/notify机制原理
    • 4. wait/notify方法的基本用法
    • 5. 线程状态的切换
    • 6. interrupt()遇到方法wait()
    • 7. notify/notifyAll方法
    • 8. wait(long)介绍
    • 9. 生产者/消费者模式
    • 10. 管道机制
    • 11. 利用wait/notify实现交叉备份
    • 12. 方法Sleep()和wait()的区别

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

1. 不使用wait/notify机制通信的缺点

下面模拟不使用wait/notify机制进行通信,然后分析其缺点所在

public class multithreadingtest {
    static volatile private List list=new ArrayList() ;   //TODO 使用volatile修饰,可以实现线程之间数据的可见性
    public static void main(String[] args) {

      Thread thread1=new Thread(new Runnable() {   //TODO 线程thread1实现循环向list中添加元素
          @Override
          public void run() {
              int i=1;
              while(i>0)
              {
                  list.add(i);  //TODO 向集合中添加元素
                  System.out.println("添加了"+i+"个元素");
                  i++;
                  try {
                      Thread.sleep(1000);  //TODO 这里睡眠是为了等待Thread2执行
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      });
        Thread thread2=new Thread(new Runnable() {   //TODO 线程thread2判读集合中元素中的数量是否达到了5
            @Override
            public void run() {
                try {
                    while(true)
                    {
                        if(list.size()==5){
                            System.out.println("thread2要退出了");
                            throw new InterruptedException();  //TODO 以抛出异常的方式结束线程
                        }
                    }
                }catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }
        });
        thread1.setName("thread1");
        thread2.setName("thread2");  //TODO 设置进程名
        thread1.start();
        thread2.start();
    }

}

在这里插入图片描述

分析代码,这里的线程间通信是指,thread1向集合中添加的数据达到5个后,通知线程2结束。虽然两个线程以集合为媒介完成了通信,但还存在缺点,thread2需要使用while循环不断轮询检测某一个条件,这样会很浪费CPU资源,所以需要引入一种机制,在减少CPU资源浪费的同时,还可以实现在多个线程之间的通信,这就是wait/notify机制。

2. 什么是wait/notify机制

wait/notify机制是Java中用于线程间通信的一种机制。它基于“监视器对象”(monitor object)的概念,通过在多个线程之间共享一个对象锁来实现线程的协作。在wait/notify机制中,线程可以调用对象的wait()方法来阻塞自己,直到其他线程调用同一个对象的notify()或notifyAll()方法来唤醒它。当一个线程调用wait()方法时,它会释放对象的锁,使其他线程能够获取该对象的锁并执行相应的操作。当其他线程调用notify()或notifyAll()方法时,它们会唤醒等待在该对象上的线程,使它们重新竞争对象的锁,继续执行操作。使用wait/notify机制可以实现一些复杂的线程协作模式,比如生产者消费者模式、读写锁模式等。但是在使用过程中需要特别注意避免死锁、竞态条件等问题。

3. wait/notify机制原理

注意:拥有相同锁的线程才可以实现wait/notify机制。
wait()方法

wait()Object类的方法,它的作用是使当前执行该方法的进程进行等待,在wait()所在的代码处暂停执行,并释放锁

  • 在调用wait之前,该线程必须要获得该对象的对象级别锁
  • 如果wait之前没有持有适当的锁,抛出IllegalMonitorStateException
  • 通过通知机制使得某个线程继续执行wait方法后面的代码,对线程的选择是按wait方法的顺序确定的(可能多个线程wait的情况)

notify()方法

该方法用于唤醒当前正在wait的线程,对线程的选择是按wait方法的顺序确定的

  • 调用notigy方法必须是在同步方法或同步块中调用,即调用之前也要获得锁,否则抛出IllegalMonitorStateException
  • 执行notify方法后,当前线程不会立即释放该锁,而是等到执行notify方法的线程将同步区代码全部执行完毕后才释放锁

4. wait/notify方法的基本用法

wait()方法使当前线程暂停运行,并释放锁

  1. 模拟一:模拟未获取锁执行wait方法
    public static void main(String[] args) throws InterruptedException {
     String name=new String();
     name.wait();
    }

在这里插入图片描述
2. 模拟二:wait()方法的正确使用

  public static void main(String[] args) throws InterruptedException {
     String name=new String();
     synchronized (name)
     {
         System.out.println("wait前");
         name.wait();
         System.out.println("wait后");
     }
    }

在这里插入图片描述
3. 模拟三:wait/notify机制的使用

  public static void main(String[] args) throws InterruptedException {
       Thread thread1=new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (list) {
                   System.out.println("thread1跑起来了");
                   try {
                       list.wait();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
                   System.out.println("thread1跑完了");
               }

           }
       });
       Thread thread2=new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (list)
               {
                   System.out.println("thread2跑起来了");
                   list.notify();
                   System.out.println("thread2跑完了");
               }
           }
       });
       thread1.start();
       Thread.sleep(3000);
       thread2.start();
    }

在这里插入图片描述

除了notify()方法外还有一个notifyAll()方法,该方法执行后会按照执行wait()方法的倒序依次唤醒“全部”的线程

5. 线程状态的切换

Thread中存在着很多的方法可以改变线程对象的状态
在这里插入图片描述

  • 创建一个新的线程对象后,再调用它的start()方法,系统会为该线程分配CPU资源,处于可运行状态(就绪状态),这个一个准备运行的状态,如果线程抢占倒cpu资源就会转移到运行状态
  • 可运行状态和运行状态是可以相互切换的。运行状态的线程可能会被高优先级的线程抢占资源从而进入就绪态
  • 线程进入可运行状态的情况
    • 调用sleep方法超过指定休眠时间
    • 线程获得了同步监视器(锁)
    • 线程在等待某个通知,而相应的线程发送了通知
    • 处于挂起(等待)状态的线程调用了resume方法
  • 暂停(等待)状态结束后,线程进入可运行状态等待系统分配资源,出现阻塞的情况大体分为5种
    • 线程调用Sleep方法,主动放弃占用的处理器资源
    • 线程调用了阻塞式I/O方法(即线程忙着处理I/O操作了),在该方法返回前,线程被阻塞
    • 线程尝试获取某个对象的锁,但锁被占用
    • 线程等待某个通知
    • 程序调用了suspend方法将该线程挂起,此方法容易导致死锁,尽量避免
  • run方法执行完毕后进入线程销毁阶段,整个线程执行完毕

Sleep()方法和wait()方法一样也会让线程陷入阻塞,但sleep并不会让持有锁的线程释放锁,而锁陷入等待(这是一种同步效果)

6. interrupt()遇到方法wait()

当线程调用wait()方法后,再对该线程对象执行interrupt()方法时,会出现InterruptedException异常

public static void main(String[] args) throws InterruptedException {
       Thread thread1=new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (list) {
                   System.out.println("thread1跑起来了");
                   try {
                       list.wait();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
                   System.out.println("thread1跑完了");
               }
           }
       });
       thread1.start();
       thread1.interrupt();
    }

在这里插入图片描述

7. notify/notifyAll方法

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

public static void main(String[] args) throws InterruptedException {
       Thread thread1=new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (list) {
                   System.out.println("第一个wait");
                   try {
                       list.wait();
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
                   list.notify();
                   System.out.println("第一个结束");
               }

           }
       });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list) {
                    System.out.println("第二个wait");
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    list.notify();
                    System.out.println("第二个结束");
                }

            }
        });
        Thread thread3=new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (list) {
                    System.out.println("第三个wait");
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    list.notify();
                    System.out.println("第三个结束");
                }

            }
        });
       Thread thread4=new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (list)
               {
                   System.out.println("开始通知");
                   list.notify();
                   System.out.println("通知结束");
               }
           }
       });
       thread1.start();
       thread2.start();
       thread3.start();
       Thread.sleep(3000);
       thread4.start();
    }

在这里插入图片描述

总结

  • 执行notify方法后,按照wait执行顺序唤醒其它进行
  • 执行同步代码块(临界区)过程中,遇到异常而导致线程终止时,锁也会被释放
  • 执行临界区代码中执行了锁所属对象的wait方法,线程会释放对象锁,等待被唤醒

nofity一次调用只能唤醒一个线程,所以容易出现部分线程对象没有被唤醒的情况。为了唤醒全部线程,可以使用notifyAll方法,它会按照执行wait方法的倒序一次对线程进行唤醒的。

8. wait(long)介绍

带有一个参数的wait(long)方法的功能是等待某一个时间内是否有线程进行通知唤醒,如果超过这个时间则自动唤醒。能继续向下运行的前提是再次持有锁。(注意也是可以在long时间内被其它线程唤醒的)

    public static void main(String[] args) throws InterruptedException {
       Thread thread1=new Thread(new Runnable() {
           @Override
           public void run() {
               synchronized (list) {
                   System.out.println("开始时间:"+System.currentTimeMillis());
                   try {
                       list.wait(2000);
                   } catch (InterruptedException e) {
                       throw new RuntimeException(e);
                   }
                   System.out.println("开始时间:"+System.currentTimeMillis());
               }
           }
       });
       thread1.start();
    }

在这里插入图片描述

9. 生产者/消费者模式

生产者/消费者模式:在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品。wait/notify机制最经典的案例“生产者与消费者模式”模式。
在这里插入图片描述
案例一:单生产者和单消费者模式
一个生产者生产,一个消费者消费,没有产品时生产者才能生产,有产品时消费者才能消费

//消费者
public class P {
    private String lock;
    public P(String lock){
        super();
        this.lock=lock;
    }
    public void GetValue(){
        try{
            synchronized (lock){
                if(ValueObject.value.equals("")){   //value不为空说明消费者还没有消费,所以生产者需要等待
                    lock.wait();
                }
                System.out.println("我现在要开始消费了!");
                lock.notify();
                System.out.println("消费的产品:"+ValueObject.value);
                ValueObject.value="";
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
//生产者
public class C {
    private String lock;
    public C(String lock){
        super();
        this.lock=lock;
    }
    public void SetValue(){
        try{
            synchronized (lock){
                if(!ValueObject.value.equals("")){   //value不为空说明消费者还没有消费,所以生产者需要等待
                    lock.wait();
                }
                System.out.println("我现在要开始生产了!");
                lock.notify();
                String value= String.valueOf(System.currentTimeMillis())+"_"+String.valueOf(System.nanoTime());
                System.out.println("生产的产品:"+value);
                ValueObject.value=value;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
//消费者线程
public class ThreadB extends Thread{
    private P p;
    public ThreadB(P p){
        super();
        this.p=p;
    }
    @Override
    public void run(){
        while(true){  //消费者消费
            p.GetValue();
        }
    }
}
//生产者线程
public class ThreadA extends Thread{
    private C c;
    public ThreadA(C c){
        super();
        this.c=c;
    }
    @Override
    public void run(){
        while(true){  //生产者不断生产
         c.GetValue();
        }
    }
}
//main函数
    public static void main(String[] args) {
        String lock=new String("");
        P p=new P(lock);
        C c=new C(lock);
        ThreadA threadA=new ThreadA(p);
        ThreadB threadB=new ThreadB(c);
        threadA.start();
        threadB.start();
    }

在这里插入图片描述
案例二:多生产者和多消费者模式的问题

在上面案例代码的基础上,多增加生产者,使得生产者的数据远远大于消费者,会出现连续生产的问题,导致生产的内容会发生覆盖,而消费者的数量远远大于生产者的数量时,会发生消费者消费空值的情况。原因是if条件发生改变时,其它的线程并不知道。同时还会出现唤醒同类的情况,最终出现连续生产或连续消费,导致程序的逻辑出现错误。解决这个问题的方法就是把if判断改为while判断。

写覆盖代码

public class multithreadingtest {

    public static void main(String[] args) {
        String lock=new String("");
        P p=new P(lock);
        C c=new C(lock);
        ThreadA[] pThread=new ThreadA[20];
        ThreadB[] rThread=new ThreadB[20];
        for (int i = 0; i < 20; i++) {
            pThread[i]=new ThreadA(c);
            pThread[i].setName("生产者"+(i+1));
            pThread[i].start();
        }
        for (int i = 0; i < 2; i++) {
            rThread[i]=new ThreadB(p);
            rThread[i].setName("消费者"+(i+1));
            rThread[i].start();
        }
    }
}

在这里插入图片描述

消费空值代码

public class multithreadingtest {

    public static void main(String[] args) {
        String lock=new String("");
        P p=new P(lock);
        C c=new C(lock);
        ThreadA[] pThread=new ThreadA[20];
        ThreadB[] rThread=new ThreadB[20];
        for (int i = 0; i < 2; i++) {
            pThread[i]=new ThreadA(c);
            pThread[i].setName("生产者"+(i+1));
            pThread[i].start();
        }
        for (int i = 0; i < 20; i++) {
            rThread[i]=new ThreadB(p);
            rThread[i].setName("消费者"+(i+1));
            rThread[i].start();
        }
    }
}

在这里插入图片描述

将多生产多消费情况下的if判断条件改为while,就不会出现连续生产和连续消费问题,即使唤醒同类线程还是会执行while判断条件,如果条件为true则执行wait方法,避免了连续生产和连续消费的情况,但程序运行过程中却出现了假死,也就是所有的线程都呈现等待状态。“假死”的现象就是线程进入等待状态,如果全部线程都进入了等待状态,程序就不执行任何业务功能了,整个项目呈现等待状态。
在这里插入图片描述
上面的情况用一种简单情形解释:老爸老妈都会做饭,你和你哥都要吃饭。老妈做完饭通知你和你哥吃饭然后去休息(释放锁开始等待),老爸一看饭做好了,不用他去做了就去休息了(生产者唤醒生产者线程),你哥还是下班的路上,你先去吃饭,结果吃完了,然后还没心没肺的通知哥回来吃饭(消费者线程唤醒消费者线程),你哥到家后一看没有饭,也去休息了,这样所有的线程都休息了,所以出现了假死。假死出现的极大可能原因是连续唤醒了同类。解决假死的方法就是将notify通知换为notifyAll通知,这样就能顺利解决假死问题。

10. 管道机制

管道机制是线程之间消息传递的一种机制,Java语言提供了各种各样的输入输出流,使我们能够很方便地操作数据,其中管道流是一种特殊的流,用于在不同线程间之间传送数据。一个线程发送数据输出到管道,另一个线程从输入管道中读数据,通过使用管道,实现不同线程间的通信,而无须借助与临时文件之类的东西。
测试一:在管道中传递字节流

//输入管道
public class wirteMethod {
    public void writMethod(PipedOutputStream out)  //参数为管道
    {
        try {
            System.out.println("wirte:");
            for (int i = 0; i < 300; i++) {
                String outData = "" + (i + 1);
                out.write(outData.getBytes()); //向管道中写
                System.out.print("~~~"+outData);
            }
            System.out.println();
            out.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
//输出管道
public class ReadDate {
   public void readMethod(PipedInputStream input){
       try{
           System.out.println("read:");
           byte[] byteArray=new byte[20];
           int readLength=input.read(byteArray);
           while (readLength!=-1)
           {
               String newData=new String(byteArray,0,readLength);
               System.out.print("____"+newData);
               readLength=input.read(byteArray);
           }
           System.out.println();
           input.close();
       } catch (IOException e) {
           throw new RuntimeException(e);
       }
   }
}
//输入线程
public class ThreadA extends Thread{
   private wirteMethod wirteMethod;
   private PipedOutputStream pipedOutputStream;
   public ThreadA(wirteMethod wirteMethod,PipedOutputStream pipedOutputStream){
       super();
       this.wirteMethod=wirteMethod;
       this.pipedOutputStream=pipedOutputStream;
   }
   @Override
    public void run(){
      wirteMethod.writMethod(pipedOutputStream);
   }
}
//输出线程
public class ThreadB extends Thread{
        private ReadDate readDate;
        private PipedInputStream pipedInputStream;
        public ThreadB(ReadDate readDate,PipedInputStream pipedInputStream){
            super();
            this.readDate=readDate;
            this.pipedInputStream=pipedInputStream;
        }
        @Override
        public void run(){
            readDate.readMethod(pipedInputStream);
        }
}
//测试
public class multithreadingtest {
    public static void main(String[] args) throws IOException, InterruptedException {
         wirteMethod  wirteMethod=new wirteMethod();
         ReadDate readDate=new ReadDate();
        PipedInputStream inputStream=new PipedInputStream();
        PipedOutputStream outputStream=new PipedOutputStream();
        inputStream.connect(outputStream);
        outputStream.connect(inputStream); //使两个管道之间产生通信链接
        ThreadB readThread=new ThreadB(readDate,inputStream);  //读线程先准备
        readThread.setName("read");
        readThread.start();
        Thread.sleep(2000);
        ThreadA wirteThread=new ThreadA(wirteMethod,outputStream);
        wirteThread.setName("witte");
        wirteThread.start();

    }
}

在这里插入图片描述
从输出结果可以看出,首先是读线程启动,由于当前管道中没有数据被写入,所以线程阻塞,知道有数据写入才继续向下运行
测试二:在管道中传递字符流(基本同上,这里就不演示了)

11. 利用wait/notify实现交叉备份

利用wati/notify机制实现交叉备份:创建20个线程,其中10个线程是向数据库A中备份数据,另外10个线程向数据库B中备份数据,要满足备份工作是交叉进行的(往A备份,然后下次往B备份,轮流交叉),重点是如何利用wait/notify让20个线程变得有序

//备份数据的代码。
public class DBTools {
    volatile private boolean prevIsa=false; //信号量
    synchronized public void backupA(){ //A备份
        try {
            while(prevIsa==true){  //prevIsa为true,此时则等待
                wait();
            }
            for (int i = 0; i < 5; i++) { //备份五次
                System.out.println("&&&&&&&&&");
            }
            prevIsa=true;
            notifyAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    synchronized public void backupB(){
        try { 
            while(prevIsa==false){  
                wait();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("*********");
            }
            prevIsa=false;
            notifyAll();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
//a备份线程
public class ThreadA extends Thread{
   private  DBTools dbTools;
   public ThreadA(DBTools dbTools){
       super();
       this.dbTools=dbTools;
   }
   @Override
    public void run(){
     dbTools.backupA();
   }
}
//b备份线程
public class ThreadB extends Thread{
    private  DBTools dbTools;
    public ThreadB(DBTools dbTools){
        super();
        this.dbTools=dbTools;
    }
    @Override
    public void run(){
        dbTools.backupB();
    }
}
//测试代码
public class multithreadingtest {

    public static void main(String[] args) throws IOException, InterruptedException {
        DBTools dbTools=new DBTools();
        for (int i = 0; i < 20; i++) {
            ThreadA threadA=new ThreadA(dbTools);
            threadA.start();
            ThreadB threadB=new ThreadB(dbTools);
            threadB.start();
        }

    }
}

在这里插入图片描述
交替打印的效果说明AB是交替进行备份的

12. 方法Sleep()和wait()的区别

方法sleep()和Wait()的区别
(1) sleep()是Thread类中的方法,二wait()是Object中的方法
(2) sleep()可以不结合Sysnchronized使用 ,而wait()必须结合
(3) sleep()在执行时不会释放锁,而wait()在执行后锁被释放
(4) sleep()方法执行后线程的状态时TIMED_WAITING,wait()方法执行后线程的状态是等待

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

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

相关文章

【操作系统】半小时写一个微型操作系统-写一个启动扇区并且导入到软盘镜像中

一.什么是启动扇区 我们使用软盘来启动操作系统时&#xff0c;系统首先就是从软盘的第一个扇区中开始读取数据&#xff0c;也就是第0面&#xff0c;0磁道的第0个扇区&#xff0c;软盘的每个扇区为512个字节的大小&#xff0c;如果最后两个字节为0xaa55&#xff08;当BIOS看到这…

Java多线程基础面试总结(一)

进程、线程和协程 进程、线程和协程 进程 进程是程序的一次执行过程&#xff0c;是系统运行程序的基本单位&#xff0c;因此进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。 在Java中&#xff0c;当我们启动main函数其实就是启动了一个JVM进程&…

【Linux】全新服务器Centos7环境搭建和安装

1、简介 最近服务器重装后,环境啥的则需要从头全部搞一遍,于是开始搞起环境的配置和安装 2、环境配置安装 前期准备 给目录文件加文件传输权限(发现无法上传文件,于是增加权限 $ chmod 766 /home/lj/ 配置DNS服务 #配置DNS服务,如果没有8.8.8.8需要添加 cat /etc/re…

WRF模式与Python融合技术在多领域中的应用及精美绘图教程

当今从事气象及其周边相关领域的人员&#xff0c;常会涉及气象数值模式及其数据处理&#xff0c;无论是作为业务预报的手段、还是作为科研工具&#xff0c;掌握气象数值模式与高效前后处理语言是一件非常重要的技能。WRF作为中尺度气象数值模式的佼佼者&#xff0c;模式功能齐全…

QML控件--Dialog

文章目录一、控件基本信息二、控件使用三、属性成员四、成员函数五、信号一、控件基本信息 Import Statement&#xff1a;import QtQuick.Controls 2.14 Since&#xff1a;Qt 5.8 Inherits&#xff1a;Popup 二、控件使用 Dialog&#xff1a; 是一个弹出窗口&#xff0c;继承…

项目打包发布流程

---》》》项目打包发布 1.编译并构建项目 2.部署 npm i npm run build scp2&#xff1a;需要写代码 ---》》》 后续有空更新&#xff1a;赋几个链接&#xff1a; Jenkins官网 nullhttps://www.jenkins.io/zh/一文详解Jenkins的安装与配置Jenkins是一个基于Java开发的开源…

【SpringMVC】10—其他概念

⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; 如果文章对你有所帮助&#xff0c;可以点赞&#x1f44d;…

2023年Web3的五大趋势

Web3 这个词有时被称为 "去中心化的互联网"&#xff0c;涵盖了一些重要的互联网发展趋势。Web3 的愿景是创造一个不受大公司控制的互联网&#xff0c;如Alibaba、baidu、Google和Facebook&#xff0c;这些公司制定了互联网的大部分规则&#xff0c;掌控着我们今天可以…

HyperWorks2021软件安装教程

下载软件 https://www.xsoftnet.com/share/a0004MWyQAg9r.html产品介绍&#xff1a; HyperWorks一款功能强大的开放式架构仿真软件。拥有先进的技术以及高性能、高效和创新的产品&#xff0c;为用户提供了设计、仿真和制造等服务。支持电磁分析设计、材料建模制造、多物理场分…

学校的地下网站(学校的地下网站1080P高清)

这个问题本身就提得有问题&#xff0c;为什么这么说&#xff0c;这是因为YouTube本身就不是一个视频网站或者说YouTube不是一个传统的视频网站&#xff01;&#xff01;&#xff01; YouTube能够一家独大&#xff0c;可不仅仅是因为有了Google 这个亲爹&#xff0c;还有一点&am…

Flutter Web 开发实践与优化

一,Flutter Web架构 目前,除了可以支持Android、iOS移动跨平台开发之外,Flutter还支持macOS、Windows、Linux和Web等多个跨平台的开发。可以说,作为一款先进的跨平台开发框架,Flutter已经真正意义上实现了“一次编写,处处运行”的美好愿景。 众所周知,Dart 语言存在之…

【建议收藏】数据库 SQL 入门——事务(内附演示)

文章目录&#x1f4da;引言&#x1f4d6;事务&#x1f4d1;事务的概念&#x1f4d1;事务操作&#x1f516;查看与设置事务提交方式&#x1f516;提交事务与回滚事务&#x1f4d1;事务的特性&#x1f4d1;并发事务问题&#x1f4d1;事务隔离级别&#x1f4cd;总结&#x1f4da;引…

8.基于拉丁超立方法的风光场景生成与削减

matlab代码&#xff1a;基于拉丁超立方法的风光场景生成与削减 摘要&#xff1a;与蒙特卡洛法不同&#xff0c;拉丁超立方采样改进了采样策略能够做到较小采样规模中获得较高的采样精度&#xff0c;属于分层抽样技术&#xff0c;设定风光出力遵从正态分布normrnd&#xff0c;从…

字节面试体验值拉满~

今天分享一位读者春招的字节二面面经&#xff0c;岗位是后端开发。 一个编程语言都没问&#xff0c;都是问网络项目mysqlredis。 问题记录 使用消息中间件降低消息持久化的压力是怎么做的&#xff0c;为什么可以降低&#xff1f; 读者答&#xff1a;在突发大量消息的情况下…

水塘抽样解决随机选择问题

1.简介 水塘抽样是一系列的随机算法&#xff0c;其目的在于从包含n个项目的集合S中选取k个样本&#xff0c;其中n为一很大或未知的数量&#xff0c;尤其适用于不能把所有n个项目都存放到内存的情况。最常见例子为Jeffrey Vitter在其论文中所提及的算法R。 2.算法步骤&#xff1…

AD823AARZ-RL-ASEMI代理亚德诺AD823AARZ-RL车规级芯片

编辑-Z AD823AARZ-RL芯片参数&#xff1a; 型号&#xff1a;AD823AARZ-RL −3dB带宽&#xff1a;17 MHz 全功率响应&#xff1a;4.8 MHz 斜率&#xff1a;30 V/s 输入电压噪声&#xff1a;14 nV/√Hz 输入电流噪声&#xff1a;1 fA/√Hz 初始偏移量&#xff1a;0.12mV …

nacos集群配置高可用数据库

1.架构 nacos集群配置高可用数据库的架构其实和nacos集群的架构差不多&#xff0c;只是在数据库方面做了主从跟keepalive实现数据库的高可用&#xff0c;当mysql的master节点挂掉时&#xff0c;keepalive的vip自动漂移到slave节点&#xff0c;并通过脚本使slave节点提升为mast…

Leetcode.1992 找到所有的农场组

题目链接 Leetcode.1992 找到所有的农场组 Rating &#xff1a; 1539 题目描述 给你一个下标从 0 开始&#xff0c;大小为 m x n 的二进制矩阵 land &#xff0c;其中 0 表示一单位的森林土地&#xff0c;1 表示一单位的农场土地。 为了让农场保持有序&#xff0c;农场土地之…

QT程序退出还占进程

问题情况 程序运行时的样子&#xff1a; 程序退出时的样子&#xff1a; 其跑到了后台进程里面&#xff1a; 程序退出了&#xff0c;但在任务管理器里查看&#xff0c;其从进程里面转移到后台进程了。 这种问题&#xff0c;怎么办&#xff0c;代码里&#xff0c;应该释放的也都…

微信小程序引入广告位功能,详细步骤!!!

大家碰到过首页加载时一开始出现的广告页面&#xff0c;这种微信官方提供了一个api进行设置&#xff0c;下面我们来详细解释一下。 首先第一步需要小程序累计用户数达到1000即可开通流量主&#xff0c;成功开通流量主之后就可以创建相应的广告位了&#xff0c;包括banner广告、…