Java多线程--生产者与消费者问题

文章目录

  • 一、生产者与消费者问题
    • (1)概要
    • (2)案例
      • 1、案例描述及需要明确的问题
      • 2、整体框架构思
      • 3、生产者和消费者的数据共享问题
      • 4、对Clerk类里面方法的设计
      • 5、测试
      • 6、唤醒机制
      • 7、两个消费者
  • 二、是否释放锁的操作
    • (1)释放锁的操作
    • (2)不会释放锁的操作

一、生产者与消费者问题

(1)概要

等待唤醒机制可以解决经典的“生产者与消费者”的问题。生产者与消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。

该问题描述了两个(多个)共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。

生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。

与此同时,消费者也在缓冲区消耗这些数据。

该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。

生产者与消费者问题中其实隐含了两个问题:

  • 线程安全问题:因为生产者与消费者共享数据缓冲区,产生安全问题。不过这个问题可以使用同步解决。
  • 线程的协调工作问题
    • 要解决该问题,就必须让生产者线程在缓冲区满时等待(wait),暂停进入阻塞状态,等到下次消费者消耗了缓冲区中的数据的时候,通知(notify)正在等待的线程恢复到就绪状态,重新开始往缓冲区添加数据。
    • 同样,也可以让消费者线程在缓冲区空时进入等待(wait),暂停进入阻塞状态,等到生产者往缓冲区添加数据之后,再通知(notify)正在等待的线程恢复到就绪状态。通过这样的通信机制来解决此类问题。

(2)案例

1、案例描述及需要明确的问题

举例:生产者&消费者

🌋案例描述

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品。

店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;

如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

类似的场景,比如厨师和服务员等。


🍻几个需要明确的问题

①是否是多线程问题?

。多线程问题至少要有两个线程。

线程有:生产者(负责生产),消费者(负责消费)。

注意店员不是,可以理解为生产者和消费者共同操作的数据。

②是否有共享数据?

。若多个线程没有共享数据的话,也不用考虑线程安全问题。

共享数据是:产品

生产者让产品增加,消费者让产品减少。

③是否有线程安全问题?

。因为有共享数据。

④是否需要考虑处理线程安全问题?如何处理?

需要。可以使用同步机制

⑤是否存在线程间的通信?如何体现?

存在

生产者生产到20就停止生产,需要wait;若消费完了,消费者也需要wait。

一旦消费到头了,当生产者又生产了一个,消费者就会被notify唤醒,继续消费。

一旦生产到头了,当消费者又消费了一个,生产者就会被notify唤醒,继续生产。

⑥如何设计解决?

面向对象的编程思想

比如线程就需要用相应类的对象去体现。

共享数据,若是简单的可以用变量体现,复杂的话可以设计成类。


2、整体框架构思

🍰分析

只要涉及到多线程问题,就需要使用两种创建多线程的方式,实现Runnable接口继承Thread类

就以继承Thread类来演示。

共享数据是“产品”,产品最终交给店员来处理了,店员来唤醒生产者和消费者。

可以将产品封装在店员这里

大方向上,有这样几个,一个店员Clerk,一个生产者Producer,一个消费者Consumer

如下:

public class ProducerConsumerTest {

}

class Clerk{    //店员

}

class Producer{ //生产者

}

class Consumer{ //消费者

}

用继承的方式来写,那么先让它们继承Thread,并重写run方法。如下:

class Producer extends Thread{ //生产者
    @Override
    public void run() {

    }
}

class Consumer extends Thread{ //消费者
    @Override
    public void run() {

    }
}

Clerk类里面定义一个产品的数量productNum,如下:

class Clerk{    //店员
    private int productNum=0;   //产品数量
}

让生产者去生产,就相当于调用Clerk里面的productNum,让它增加;消费者则是让它减少。

增加/减少通常用方法来体现。(对属性的修改通常用方法来体现)

如下:

class Clerk{    //店员
    private int productNum=0;   //产品数量

    //增加产品数量的方法(对属性的修改通常用方法来体现)
    public void addProduct(){

    }

    //减少产品数量的方法
    public void minusProduct(){

    }
}

3、生产者和消费者的数据共享问题

现在生产者只需要调用Clerk类里面的addProduct()方法即可,如何去调用呢?

其实现在操作的就是共享数据,但是目前生产者与消费者没有体现共享的概念啊?

因为产品数量声明在Clerk类里面的,那我们就来声明一个Clerk类型的变量。如下:

image.png

给这个clerk变量赋值,可以使用构造器:(通过构造器的方式给clerk初始化)

class Producer extends Thread{ //生产者
    private Clerk clerk;

    //构造器
    public Producer(Clerk clerk){
        this.clerk=clerk;
    }

    //...
}

那么在run方法里面,就可以使用clerk,让它调用addProduct()来生产产品。如下:

class Producer extends Thread{ //生产者
    private Clerk clerk;

    //构造器
    public Producer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("生产者开始生产产品啦...");

            clerk.addProduct();
        }
    }
}

当然,可以让它生产慢一点,在这里sleep()一下:

class Producer extends Thread{ //生产者
    private Clerk clerk;

    //构造器
    public Producer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("生产者开始生产产品啦...");

            try {
                Thread.sleep(50);   //50ms生产一个产品
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.addProduct();
        }
    }
}

消费者里面是同样的逻辑:

class Consumer extends Thread{ //消费者
    private Clerk clerk;

    public Consumer(Clerk clerk){   //通过构造器给clerk赋值
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("消费者开始消费产品喽...");

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.minusProduct();
        }
    }
}

通过构造器给clerk赋值,在具体造线程的时候,放同一个clerk即可保证“共享”。

🎲有的小伙伴可能会疑惑:这个clerk不是每个类中的私有属性吗,怎么就共享了呢?

其实各自的Clerk都不是自己new的,可以在构造初始化的时候给他们传同一个clerk对象

Java只有值传递,传一个类的时候实际传递的是堆中的空间地址,因此是共享的。

之前说过一个案例,不同的Consumer,在创建多个线程的时候,用的是同一个Account act,那自然就是共享的。如下:

public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        Customer customer1=new Customer(acct,"小旺");
        Customer customer2=new Customer(acct,"小岁");

        customer1.start();
        customer2.start();
    }
}

class Account{  //账户
    private double balance; //余额

    public synchronized void deposit(double amt){   //this:是唯一的,即为actt
        if(amt>0){
            balance+=amt;
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
    }
}

class Customer extends Thread{
    Account account;

    //构造器
    public Customer(Account acct){
        this.account=acct;
    }

    public Customer(Account acct,String name){
        super(name);
        this.account=acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.deposit(1000);
        }
    }
}

4、对Clerk类里面方法的设计

接下来两个线程的执行都到Clerk类里面去操作了。

所以我们将重心放在Clerk里面。

首先是增加产品,最先想到的就是让产品增加一下:

class Clerk{    //店员
    private int productNum=0;   //产品数量

    //增加产品数量的方法(对属性的修改通常用方法来体现)
    public void addProduct(){
        productNum++;   //让产品加一
        System.out.println(Thread.currentThread().getName()+"生产了第"+productNum+"个产品");
    }
}

比如现在的产品数量是0,调用addProduct()方法的时候,productNum加一,然后输出”某线程生产了第1个产品“。

有一个需要注意的,让产品增加的时候,不能无限制增加。

需要判断一下,若是产品数量大于等于20的话,需要等待,不能继续生产了。如下:

image.png

记得处理一下wait()的异常。

wait()需要使用在同步代码块或同步方法当中,productNum就是共享数据,而addProduct()方法就是在操作共享数据,所以

我们可以将addProduct()声明为共享方法,如下:

image.png

同步监视器是thiswait()的调用者也是this,所以此时没有问题。

若产品数量大于等于20wait()等待,不满足这个条件就生产产品。

接下来看一下消费者,注意需要先输出再让产品减一:

//减少产品数量的方法
public void minusProduct(){

    System.out.println(Thread.currentThread().getName()+"消费了第"+productNum+"个产品");
    productNum--;   //让产品减一
}

当然,也不能无限制消费,当产品数量小于等于0的时候,需要等待:

image.png

Clerk的代码如下:

class Clerk{    //店员
    private int productNum=0;   //产品数量

    //增加产品数量的方法(对属性的修改通常用方法来体现)
    public synchronized void addProduct(){  //wait()需要在同步代码块或同步方法中去使用
        if(productNum>=20){ //产品数量大于等于20
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        productNum++;   //让产品加一
        System.out.println(Thread.currentThread().getName()+"生产了第"+productNum+"个产品");
    }

    //减少产品数量的方法
    public synchronized void minusProduct(){    //wait()需要在同步代码块或同步方法中去使用
        if(productNum<=0){  //产品数量小于等于0
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(Thread.currentThread().getName()+"消费了第"+productNum+"个产品");
        productNum--;   //让产品减一
    }
}

5、测试

在测试类ProducerConsumerTest里面创建共享数据clerk,然后创建生产者消费者对象。

如下:

public class ProducerConsumerTest {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();    //clerk本身就充当了共享数据

        Producer pro1=new Producer(clerk);  //创建生产者对象pro1
        Consumer con1=new Consumer(clerk);  //创建消费者对象con1
    }
}

起个名字,然后各自去调用start()方法,它们就各自去执行各自的run()方法。

如下:

public class ProducerConsumerTest {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();    //clerk本身就充当了共享数据

        Producer pro1=new Producer(clerk);  //创建生产者对象pro1
        Consumer con1=new Consumer(clerk);  //创建消费者对象con1

        pro1.setName("生产者1");
        con1.setName("消费者1");

        pro1.start();
        con1.start();
    }
}

🌱代码

package yuyi04.Communication;

/**
 * ClassName: ProducterConsumerTest
 * Package: yuyi04.Communication
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/2/2 0002 17:15
 */
public class ProducerConsumerTest {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();    //clerk本身就充当了共享数据

        Producer pro1=new Producer(clerk);  //创建生产者对象pro1
        Consumer con1=new Consumer(clerk);  //创建消费者对象con1

        pro1.setName("生产者1");
        con1.setName("消费者1");

        pro1.start();
        con1.start();
    }
}

class Clerk{    //店员
    private int productNum=0;   //产品数量

    //增加产品数量的方法(对属性的修改通常用方法来体现)
    public synchronized void addProduct(){  //wait()需要在同步代码块或同步方法中去使用
        if(productNum>=20){ //产品数量大于等于20
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        productNum++;   //让产品加一
        System.out.println(Thread.currentThread().getName()+"生产了第"+productNum+"个产品");
    }

    //减少产品数量的方法
    public synchronized void minusProduct(){    //wait()需要在同步代码块或同步方法中去使用
        if(productNum<=0){  //产品数量小于等于0
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(Thread.currentThread().getName()+"消费了第"+productNum+"个产品");
        productNum--;   //让产品减一
    }
}

class Producer extends Thread{ //生产者
    private Clerk clerk;

    //构造器
    public Producer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("生产者开始生产产品啦...");

            try {
                Thread.sleep(50);   //50ms生产一个产品
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.addProduct();
        }
    }
}

class Consumer extends Thread{ //消费者
    private Clerk clerk;

    public Consumer(Clerk clerk){   //通过构造器给clerk赋值
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("消费者开始消费产品喽...");

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.minusProduct();
        }
    }
}

🍺输出(部分)

image.png

可以看到,消费者消费的时候没有产品,就会wait(),因为没有唤醒机制,生产者就会一直生产,生产到第20个的时候也开始wait(),然后程序就在一直等待。

6、唤醒机制

刚才没有唤醒机制,导致程序无法执行结束,两个线程一直等待。

🎲那什么时候唤醒呢?

对于生产者来说,当生产满20就wait()等待,当消费者消费了1个就可以唤醒它继续生产了。

对于消费者来说,当没有可以消费的产品的时候(即产品小于等于0的时候)就进入wait()等待,当生产者生产了1个产品就可以唤醒它继续消费了。

所以,生产者生产了1个产品就可以唤醒消费者,消费者消费了1个产品就可以唤醒生产者。互相唤醒

如下:

image.png

🌱代码

package yuyi04.Communication;

/**
 * ClassName: ProducterConsumerTest
 * Package: yuyi04.Communication
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/2/2 0002 17:15
 */
public class ProducerConsumerTest {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();    //clerk本身就充当了共享数据

        Producer pro1=new Producer(clerk);  //创建生产者对象pro1
        Consumer con1=new Consumer(clerk);  //创建消费者对象con1

        pro1.setName("生产者1");
        con1.setName("消费者1");

        pro1.start();
        con1.start();
    }
}

class Clerk{    //店员
    private int productNum=0;   //产品数量

    //增加产品数量的方法(对属性的修改通常用方法来体现)
    public synchronized void addProduct(){  //wait()需要在同步代码块或同步方法中去使用
        if(productNum>=20){ //产品数量大于等于20
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        productNum++;   //让产品加一
        System.out.println(Thread.currentThread().getName()+"生产了第"+productNum+"个产品");

        //生产了一个就可以唤醒消费者
        notify();
    }

    //减少产品数量的方法
    public synchronized void minusProduct(){    //wait()需要在同步代码块或同步方法中去使用
        if(productNum<=0){  //产品数量小于等于0
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println(Thread.currentThread().getName()+"消费了第"+productNum+"个产品");
        productNum--;   //让产品减一

        //只要消费者消费了一个,就可以唤醒生产者继续生产
        notify();
    }
}

class Producer extends Thread{ //生产者
    private Clerk clerk;

    //构造器
    public Producer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("生产者开始生产产品啦...");

            try {
                Thread.sleep(50);   //50ms生产一个产品
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.addProduct();
        }
    }
}

class Consumer extends Thread{ //消费者
    private Clerk clerk;

    public Consumer(Clerk clerk){   //通过构造器给clerk赋值
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("消费者开始消费产品喽...");

            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.minusProduct();
        }
    }
}

🍺输出(部分)

PixPin_2024-02-03_11-07-53.gif

可以看到一直在生产和消费,停不下来了。

这是因为我们设置的生产和消费sleep时间都是一样的50ms,所以生产一个再消费一个,停不下来了。

现在我们将消费改为100ms,消费慢一点。如下:

image.png

再次输出:

PixPin_2024-02-03_11-17-08.gif

生产的比较快,到20的时候会有等待的行为,然后消费一个,生产者再生产一个,无止尽。


7、两个消费者

现在我们再整一个消费者con2,如下:

public class ProducerConsumerTest {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();    //clerk本身就充当了共享数据

        Producer pro1=new Producer(clerk);  //创建生产者对象pro1
        Consumer con1=new Consumer(clerk);  //创建消费者对象con1
        Consumer con2=new Consumer(clerk);  //创建消费者对象con1

        pro1.setName("生产者1");
        con1.setName("消费者1");
        con2.setName("消费者2");

        pro1.start();
        con1.start();
        con2.start();
    }
}

记得在生产了一个产品的时候将两个消费者都唤醒

image.png

还有一个小细节,生产者将两个消费者都唤醒之后,两个消费者就会从被wait()的位置继续向下执行,而此时只有1个商品了。

这两个消费者可能都消费商品,某一个消费者消费商品使得产品数量变成了0,另一个消费者再去消耗产品就会使得产品数量变成了负数,不太合适。

如下:

image.png

所以我们需要将这些逻辑写入else中:

image.png

此时消费者若是唤醒之后,需要重新进入同步方法,去握锁。

已经在if语句里的wait()被唤醒后,就不会再进入else,而是直接跳出这个if-else语句,去抢minusProduct方法的,两个方法谁先抢到,谁就先消耗资源,后进来那经过判断进入if语句,休眠。

当线程多个wait时,同时全部唤醒是有可能一次性执行代码的,这是因为:wait解除之后代码不是从头开始执行,而是从wait开始。

对应的,生产者也稍微调整一下代码:

image.png


🌱代码

package yuyi04.Communication;

/**
 * ClassName: ProducterConsumerTest
 * Package: yuyi04.Communication
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/2/2 0002 17:15
 */
public class ProducerConsumerTest {
    public static void main(String[] args) {
        Clerk clerk=new Clerk();    //clerk本身就充当了共享数据

        Producer pro1=new Producer(clerk);  //创建生产者对象pro1
        Consumer con1=new Consumer(clerk);  //创建消费者对象con1
        Consumer con2=new Consumer(clerk);  //创建消费者对象con1

        pro1.setName("生产者1");
        con1.setName("消费者1");
        con2.setName("消费者2");

        pro1.start();
        con1.start();
        con2.start();
    }
}

class Clerk{    //店员
    private int productNum=0;   //产品数量

    //增加产品数量的方法(对属性的修改通常用方法来体现)
    public synchronized void addProduct(){  //wait()需要在同步代码块或同步方法中去使用
        if(productNum>=20){ //产品数量大于等于20
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            productNum++;   //让产品加一
            System.out.println(Thread.currentThread().getName()+"生产了第"+productNum+"个产品");

            //生产了一个就可以唤醒消费者
            notifyAll();
        }
    }

    //减少产品数量的方法
    public synchronized void minusProduct(){    //wait()需要在同步代码块或同步方法中去使用
        if(productNum<=0){  //产品数量小于等于0
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+"消费了第"+productNum+"个产品");
            productNum--;   //让产品减一

            //只要消费者消费了一个,就可以唤醒生产者继续生产
            notifyAll();
        }

    }
}

class Producer extends Thread{ //生产者
    private Clerk clerk;

    //构造器
    public Producer(Clerk clerk){
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("生产者开始生产产品啦...");

            try {
                Thread.sleep(50);   //50ms生产一个产品
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.addProduct();
        }
    }
}

class Consumer extends Thread{ //消费者
    private Clerk clerk;

    public Consumer(Clerk clerk){   //通过构造器给clerk赋值
        this.clerk=clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("消费者开始消费产品喽...");

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.minusProduct();
        }
    }
}

🍺输出(部分)

image.png

现在就没什么问题了。

二、是否释放锁的操作

任何线程进入同步代码块同步方法之前,必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?

(1)释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束。
  • 当前线程在同步代码块、同步方法中遇到breakreturn终止了该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致当前线程异常结束。
  • 当前线程在同步代码块、同步方法中执行了锁对象的wait()方法,当前线程被挂起,并释放锁。

(2)不会释放锁的操作

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()Thread.yield()方法暂停当前线程的执行。
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该该线程挂起,该线程不会释放锁(同步监视器)。
  • 应尽量避免使用suspend()resume()这样的过时来控制线程。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PMP备考的三个阶段及学习方法分享

PMP证书是项目管理必备的关键技能证书&#xff0c;是具备进行项目管理的重要技能体现。无论升职加薪&#xff0c;还是从事项目管理工作&#xff0c;都非常重要。 个人主要从事产品开发工作&#xff0c;开始逐渐承担一些项目经理角色&#xff0c;但目前项目管理知识薄弱&#x…

探讨深浅拷贝在js加密中的运用

深浅拷贝是JavaScript中常用的概念&#xff0c;用于复制对象或数组。它们在处理数据时有不同的用途&#xff0c;适用于不同的场景。在本文中&#xff0c;我们将详细介绍深浅拷贝的概念&#xff0c;提供案例代码&#xff0c;并探讨它们在JavaScript中的应用场景&#xff0c;以及…

@JsonFormat 和 @@DateTimeFormat 时间格式化注解详解(一篇带你解决问题)

前后数据交互过程中&#xff0c;Date类型的数据经常会出现类型映射转换的错误&#xff0c;为了达到业务的目标时间格式&#xff0c;通常会使用JsonFormat 和 DateTimeFormat&#xff0c;但是这两者有什么区别呢&#xff1f; 一、示例代码 先准备一个POJO&#xff0c;拥有Date类…

PPT录屏功能在哪?一键快速找到它!

在现代办公环境中&#xff0c;ppt的录屏功能日益受到关注&#xff0c;它不仅能帮助我们记录演示文稿的播放过程&#xff0c;还能将操作过程、游戏等内容完美录制下来。可是很多人不知道ppt录屏功能在哪&#xff0c;本文将为您介绍ppt录屏的打开方法&#xff0c;以帮助读者更好地…

本体论(ontology)在工业4.0中的应用

信息技术中的本体与哲学的本体论是不同的&#xff0c;它代表了某个专业领域的基本概念&#xff0c;它们在智能制造和工业4.0 中具有不可或缺的作用&#xff0c;为了实现人与机器&#xff0c;机器与机器之间的确定性操作。一个标准化的&#xff0c;精确定义的本体服务是非常重要…

【XR806开发板试用】xr806使用tcp socket与手机通信

本文为极术社区XR806开发板活动试用文章。 参考&#xff1a;基于星辰处理器的全志XR806开源鸿蒙开发板上手体验 搭建环境。并成功编译。 项目源码 &#xff1a; https://gitee.com/kingwho/smart-home 在同一个局域网中&#xff0c;手机与xr806连接后&#xff0c;手机 APP 每隔…

【开源】JAVA+Vue+SpringBoot实现就医保险管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 科室档案模块2.2 医生档案模块2.3 预约挂号模块2.4 我的挂号模块 三、系统展示四、核心代码4.1 用户查询全部医生4.2 新增医生4.3 查询科室4.4 新增号源4.5 预约号源 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVue…

【对象属性拷贝】⭐️按照需要转换的类型反射设置拷贝后对象的属性

背景&#xff1a; 小伙伴们大家好&#xff0c;最近开发的时候遇到一种情况&#xff0c;项目引入了全局序列化器来实现Date&#xff0c;LocalDateTime类型的字段根据时区转换&#xff0c;总体来说接口没什么要改动的&#xff0c;只要原来字段的属性是以上两种就行&#xff0c;但…

Linux/Uinx 系统编程:进程管理(3)

Linux/Uinx 系统编程&#xff1a;进程管理&#xff08;3&#xff09; 本章来讲解进程管理的最后一部分内容。 文章目录 Linux/Uinx 系统编程&#xff1a;进程管理&#xff08;3&#xff09;I/O重定向原理FILE结构体的内部结构重定向的实现过程 scanf 与 printfscanfprintf 重定…

linux 组建和卸载raid1、raid0详细操作

组raid的最好是相同容量和型号的硬盘&#xff0c;否则会有木桶效应 linux下组raid有很多细节 一、安装raid软件 deb包 apt-get install mdadm或dnf包 dnf install mdadm二、组raid1-镜像&#xff0c;组raid0-并列 raid1和raid0只有在madam命令时一点点不同&#xff0c;其他…

了解野指针与assert断言 拿捏指针的使用!

目录 1.野指针 野指针的成因&#xff1a; 2.规避野指针 3.assert断言 创作不易&#xff0c;宝子们&#xff01;如果这篇文章对你们有帮助的话&#xff0c;别忘了给个免费的赞哟~ 1.野指针 概念&#xff1a;野指针就是指针指向的位置是不可知的&#xff08;随机的、不正确的…

重写Sylar基于协程的服务器(5、IO协程调度模块的设计)

重写Sylar基于协程的服务器&#xff08;5、IO协程调度模块的设计&#xff09; 重写Sylar基于协程的服务器系列&#xff1a; 重写Sylar基于协程的服务器&#xff08;0、搭建开发环境以及项目框架 || 下载编译简化版Sylar&#xff09; 重写Sylar基于协程的服务器&#xff08;1、…

【论文阅读笔记】Taming Transformers for High-Resolution Image Synthesis

Taming Transformers for High-Resolution Image Synthesis 记录前置知识AbstractIntroductionRelated WorkMethodLearning an Effective Codebook of Image Constituents for Use in TransformersLearning the Composition of Images with Transformers条件合成合成高分辨率图…

阿狸与小兔子的奇幻之旅

在很久很久以前&#xff0c;有一个遥远的国度&#xff0c;这个国度里生活着各种各样的动物&#xff0c;它们和谐共处&#xff0c;幸福快乐。在这个国度里&#xff0c;有一只聪明伶俐的小狐狸&#xff0c;名叫阿狸。 一天&#xff0c;阿狸在森林里散步时&#xff0c;遇到了一只正…

【BIAI】Lecture10 - Motor System2

Motor System2 专业术语 descending spinal tracts 下行脊髓束 corticospinal tract 锥体束 reticulospinal tract 脊髓脑干束 vestibulospinal tract 脊髓脑干侧脊束 precentral gyrus 前中央回 population coding 群体编码 basal ganglia 基底节 thalamus 丘脑 Posterior pa…

Python中容器类型的数据

目录 序列 序列的索引操作 加和乘操作 切片操作 成员测试 列表 创建列表 追加元素 插入元素 替换元素 删除元素 元组 创建元组 元组拆包 集合 创建集合 修改集合 字典 创建字典 修改字典 访问字典视图 遍历字典 若我们想将多个数据打包并且统一管理&…

计算斐波那契数

前提需备知识&#xff1a; 斐波那契数列是第一项和第二项为1&#xff0c;第三项为前两项之和&#xff0c;然后以此类推的一个数列&#xff0c;即1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8&#xff0c;13&#xff0c;21&#xff0c;34&#xff0c;5…

【开源】JAVA+Vue.js实现学生日常行为评分管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、系统设计2.1 功能模块设计2.2.1 登录注册模块2.2.2 用户管理模块2.2.3 评分项目模块2.2.4 评分数据模块2.2.5 数据字典模块 2.3 可行性设计2.4 用例设计2.5 数据库设计2.5.1 整体 E-R 图2.5.2 用户2.5.3 评分项目2.5.4 评分数据2.5.…

STM32MP135开发板助力电力行业,IEC61850协议移植笔记

1.概述 IEC61850是变电站自动化系统&#xff08;SAS&#xff09;中通信系统和分散能源&#xff08;DER&#xff09;管理的国际标准。它通过标准的实现&#xff0c;实现了智能变电站的工程运作标准化。使得智能变电站的工程实施变得规范、统一和透明&#xff0c;在电力和储能系…

评论区功能的简单实现思路

评论区功能是社交类项目中的核心组成部分&#xff0c;它涉及到前端的交云和后端的数据处理。基于你的技术栈&#xff08;前端 Vue3&#xff0c;后端 Java&#xff09;&#xff0c;下面是一个具体的实现思路和数据库设计建议&#xff0c;并探索一下知乎的评论系统。 数据库设计…