Java EE|多线程代码实例之单例模式与阻塞队列

news2024/11/15 18:44:48

文章目录

    • 前言
      • 设计模式介绍
    • 🔴单例模式
        • 什么是单例模式
        • 单例模式实现方式
          • 饿汉模式
          • 懒汉模式
        • 基于上述单例模式实现线程安全问题讨论
        • 重点回顾
    • 🔴阻塞队列
        • 阻塞队列是什么
        • 标准库中的阻塞队列
        • 典型应用场景:生产者消费者模型
        • 利用系统提供的BlockingQueue实现生产者消费者模型
        • 阻塞队列的实现
        • 自实现阻塞队列下的生产者消费者模型
    • 参考

前言

设计模式介绍

(一)设计模式概念

设计模式类似上古流传的棋谱,是一种大佬总结出来的固定的套路,典型场景下的典型解决方案,相当于解题套路。

【框架与设计模式区别】框架是硬性的、不按照框架写,代码跑不起来,设计模式是软性的,不遵守,代码也能跑起来,但是可能可读性、可维护性、可扩展性都不太行。

(二)常见的设计模式

大佬设计的设计模式有很多,我们目前主要掌握这两个,剩下的以后再掌握完全ok

  • 单例模式
  • 工厂模式

🔴单例模式

什么是单例模式

单例模式能保证某个类中只存在唯一的实例,不能够创建多个实例。实际开发中,这种需求不算少。例如,JDBC中DataSource实例中只需要一个。单例模式从“法律”上硬性规定了类的实例对象个数。类的实例化出来的东西不是叫对象吗,其实也可以叫做实例。这里的单例,就是只实例化出一个对象。

单例模式实现方式

java中,单例模式实现方式其实有很多种,但是我们目前主要掌握饿汉和懒汉模式两种即可。

饿汉模式

饿汉模式中的饿汉是对实例对象创建时机的形象化描述,也就是说创建的比较早。下边我们边写代码边解释。

class Singleton{
    public static Singleton singleton=new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return singleton;
    }
}
//for test
public class Code22_SingletonHungry {
    public static void main(String[] args) {
        Singleton s1= Singleton.getInstance();
        Singleton s2= Singleton.getInstance();
        System.out.println(s1==s2);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3IBuLj0v-1673617220932)(F:\typora插图\image-20230113165211098.png)]

可以,看到此时无论去访问几次getInstance,得到的实例都是同一个。

这里,我们在类定义时就直接创建对象。此时对象就会在类加载阶段就直接被创建,相较于一般对象创建时机比较早,所以称为“饿汉”。这里,我们再补充一下,什么是类加载阶段。例如,java代码Hello.java,通过javac变成java.class,这是编译阶段。而类加载过程是运行一个java程序,就让java进程就能找到,并读取对应的.class文件(找文件有一定的规则,在后续学习jvm会有进一步的理解),进行内容解析,根据内容构造类对象,这一系列的过程叫做类加载。

饿汉模式如何确保创建对象是单例的?类定义时创建静态对象+私有构造方法,公开接口get实例,并且设置成静态确保可利用类名直接调用。

懒汉模式

这里的懒汉也是一种形象化的说明,是指实例的常见不需要就不创建,需要时再创建,并且以后再尝试获取的都是第一次创建的。下边我们来看一下代码。

class SingletonL {
    public static SingletonL singleton=null;
    private SingletonL(){}
    public static SingletonL getInstance(){
        if(singleton==null){
            singleton=new SingletonL();
        }
        return singleton;
    }
}
//for test
public class Code23_SingletonLazy {
    public static void main(String[] args) {
        SingletonL s1= SingletonL.getInstance();
        SingletonL s2= SingletonL.getInstance();
        System.out.println(s1==s2);
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rS2ULZi7-1673617220933)(F:\typora插图\image-20230113165901408.png)]

显然,此时是在第一次尝试获取对象的时候才进行创建的。

并且,懒汉模式确保单例是,通过创建静态对象变量+需要时创建对象+提供公开的接口并且设置成静态方法,私有化构造方法。

基于上述单例模式实现线程安全问题讨论

目前,在单线程环境下,饿汉模式、懒汉模式这两种实现单例模式运行结果都是正确的,那么在多线程环境下呢?也就是说这两种实现方式哪一种更可能出现线程不安全问题?应该怎样尽量规避?

经过思考,我们不难发现多线程环境下,懒汉模式更容易出现不安全问题。饿汉模式下,对象在类加载阶段就已经创建好了,在多线程环境运行下,只有读操作,显然没有线程安全问题;而懒汉模式则是getInstance方法中有创建对象的操作,也就是说懒汉模式下即有读操作也有写操作。

在详细讨论之前,我们需要知道实例化一个对象即new操作实际上是有好几步组成的。分别是1.创建内存空间2.调用构造方法,把这个内存空间初始化称为一个合理的对象3.把内存空间的地址赋给实例的引用。

首先,在getInstance方法中,判断部分的逻辑中,有可能中了singleton==null的逻辑,进入之后,new了一个对象,但是还有把这个对象赋值给对象变量,此时cpu被另一个线程调度走了,也就是说当前的singleton变量不是任何对象的引用,此时再次中了if逻辑,又重新new了一次,又这是非原生类,没有常量池概念,所以显然两次new的对象不同,此时我们假设cpu没有被其他线程调度走,那么此时就会返回。之后再回到最开始的线程,将第一次new的对象赋值并返回,显然就破坏了我们想要的单例情况。

其次,上边我们已经提到了new这个动作其实分为好几个操作,所以这里很可能会出现编译器优化的一种情况——指令重排序,原来的不是123吗,这里可能就会变成132,就会出现非法对象,只有实际对象地址,但是却没实质东西。

显然,造成此时线程不安全的第一种情况就是操作不是原子的,对于此我们可以采取加锁的方式解决。至于加锁的对象,虽然这里是在类内,但是由于这里是静态方法,没有隐含的this指针,所以我们这里的加锁对象是类对象SingletonL.class。

造成此线程不安全的第二个原因就是指令重排序,对此我们采取的解决办法就是对singleton变量加volatile关键字,提醒编译器。除此以外,volatile还可以解决内存可见性问题,虽然这里没有涉及到。

那么我们的代码就可以优化成这样

class SingletonL {
    volatile public static SingletonL singleton=null;
    private SingletonL(){}
    public static SingletonL getInstance(){
        synchronized (SingletonL.class) {
            if(singleton==null){
                singleton=new SingletonL();
            }
        }
        return singleton;
    }
}
//for test
public class Code23_SingletonLazy {
    public static void main(String[] args) {
        SingletonL s1= SingletonL.getInstance();
        SingletonL s2= SingletonL.getInstance();
        System.out.println(s1==s2);
    }
}

这里我们尽可能的解决了线程不安全问题。但是我们知道加锁这种解决线程安全的方法是有部分效率消耗换来的,那么有没有一种可能我们既能尽可能的解决线程安全问题,同时又保证代码执行效率尽可能的高?

当然有。我们试着思考,上边我们的加锁主要是为了防止读写这两种操作没有捆绑而导致的线程不安全,而写操作只是在一开始没有new的时候会触发,其他都是读操作。那么我们有没有一种可能判断这是不是需要写操作参与,如果是就加锁,如果不是就不加锁?听起来是可行的,那么我们如何实现呢?经过思考,我们不难发现,写<==>instance是空的,所以我们的代码就可以优化成这样。

class SingletonL {
    volatile public static SingletonL singleton=null;
    private SingletonL(){}
    public static SingletonL getInstance(){
        if(singleton==null){
            synchronized (SingletonL.class) {
                if(singleton==null){
                    singleton=new SingletonL();
                }
            }
        }
        return singleton;
    }
}
public class Code23_SingletonLazy {
    public static void main(String[] args) {
        SingletonL s1= SingletonL.getInstance();
        SingletonL s2= SingletonL.getInstance();
        System.out.println(s1==s2);
    }
}

重点回顾

1.写操作是原子的==>加锁

2.new操作可能出现指令重排序==>加volatile关键字

3.效率提高==>判断是不是需要加锁===>两个if

🔴阻塞队列

阻塞队列是什么

我们已经知道普通队列具有先进先出的特性,事实上还有一些特殊队列,并不一定遵守先进先出的规则,它们往往带有一些特殊功能。

常见的功能队列,除了阻塞队列,还有单调队列/优先级队列、消息队列等。

阻塞队列:具有阻塞/等待功能的队列。如果当前队列已满,禁止入队,尝试入队操作所在的线程进入当前对象的阻塞队列;如果当前队列为空,禁止出队,尝试出队操作所在线程进入当前对象的阻塞队列。

单调队列/优先级队列:具有优先级的队列,优先级的确定规则可以人为规定。

消息队列:是在阻塞队列基础上加了“消息类型 ”,按照“消息类型”进行先进先出。这是一种数据结构。而由于消息队列应用的过于广泛,所以,有业界大佬将其实现成为了一个类似mysql一样的基于客户端-服务器功能工作的程序。此时它的存储能力和转发能力都大大提高,实际开发中很多大型项目都会有它的影子。如果想更好的理解它就必须先理解好阻塞队列。

标准库中的阻塞队列

标准库中提供了实现阻塞队列功能的类和接口。虽然说,阻塞队列本质上还是一个队列,也就是说实现了Queue接口,也有普通队列有的offer、poll等方法,但是我们使用阻塞队列主要使用的不是这些,而是它特有的阻塞功能,此时对应的入队和出队操作的方法分别对应的是put和take方法。我们这里主要演示这两种方法。

另外,实现此接口的类常见的有ArrayBlockingQueue,背后的数据结构是数组;LinkedBlockingQueue,背后的数据结构是链表,还有PriorityBlockingQueue,背后的数据结构是堆。除此以外还有双端队列的阻塞队列,但目前我们主要掌握前三种。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vEuv76JA-1673617220934)(F:\typora插图\image-20230113182243850.png)]

public class Code24_BlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> blockingQueue=new LinkedBlockingQueue<>();
        blockingQueue.put(5);
        blockingQueue.put(3);
        blockingQueue.put(4);
        blockingQueue.put(2);
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02XTwHuE-1673617220935)(F:\typora插图\image-20230113182702315.png)]

这里取完了,再取进入阻塞比较好演示;但是放满了,再放不是特别好演示,以及目前个人水平的有限,所以只给出我结合网上资料以及个人对源码的理解给出一个暂时的结论,如若发现,会立即更正。

数组实现的阻塞队列——ArrayBlockingQueue:没有无参构造函数,必须传入初始容量,并且目前没发现同ArrayList等类相似的扩容机制。

链表实现的阻塞队列——LinkedListBlockingQueue:一般来讲,链表没有最大容量的概念,这里提供了一个无参构造函数。基于链表实现的阻塞队列规定了最大容量是整形的最大值。

堆实现的阻塞队列——PriorityQueueBlockingQueue:提供了无参构造函数、有参构造(只容量的、只比较器的、容量加比较器的)。同时无参构造下,阻塞队列有一个默认的容量11,同时,基于堆实现的阻塞队列可以进行扩容操作。可以说,它基本上无界的阻塞队列。

基于上述原因,满了不能放的阻塞情况不是特别好演示,所以请大家自行“脑补”

典型应用场景:生产者消费者模型

正因为阻塞队列重要的阻塞特性与现实很多场景有重合,所以,基于此,大佬开发了“生产者消费者模型”。我们下边来详细了解一下。

对于阻塞队列,生产者是添加元素的一方,消费者是取元素的一方,产品是阻塞队列的元素。生产者和消费者通过阻塞队列相互联系。

这样说可能会比较抽象,所以下边我们来举个例子

例如:服务器之间的相互调用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TYW9ob7Q-1673617220935)(F:\typora插图\image-20230113203917457.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TbQaRXwC-1673617220936)(F:\typora插图\image-20230113203937981.png)]

生产者消费者模型是一种非常典型的开发模型。它有什么作用呢?第一,它实现了发送方和接受方的解耦(白话:关联程度)。

第二,它可以削峰填谷,保证系统稳定性。这句话可能比较抽象,我们试着想象,假定你在维护一个服务器,突然很多用户同一时刻发送请求,那么如果服务的负载能力不强,承受不了大规模的冲击,很可能会崩掉。但是如果我们使用一个负载能力非常强(即队列容量非常大)的阻塞队列作为缓冲,就可以很大程度的缓解这个问题,即使某一时刻用户请求量非常大,负责具体业务的服务器也能正常工作,或者某一时刻用户请求量非常少,负责具体业务的服务器也不会过于悠闲。

利用系统提供的BlockingQueue实现生产者消费者模型

public class Code27_BQCP {
    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue=new LinkedBlockingQueue<>();
        Thread customer=new Thread(()->{
            while(true){
                try {
                    Thread.sleep(500);
                    int thing= (blockingQueue.take());
                    System.out.println("消费元素:"+thing);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread producer=new Thread(()->{
            int count=0;
            while(true){
                try {
                    blockingQueue.put(count);
                    count++;
                    System.out.println("生产元素:"+count);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        //这里顺序不用手动控制,因为是在阻塞队列中,随便的顺序都可以
        customer.start();
        producer.start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UprIZtz5-1673617220937)(F:\typora插图\image-20230113205315449.png)]

观察运行结果,很容易看出,生产元素和消费元素基本上成对出现的,同时,不会出现要消费了,元素还没有生产出来的情况。

阻塞队列的实现

(一)思路分析

这里我们使用数组实现阻塞队列,因为阻塞队列只是在原来的基础上加了个特殊功能,类比数组实现普通队列,很容知道,此处我们需要使用逻辑上的“循环/环形数组”

我们要想实现阻塞队列,那么首先实现一个普通队列,只不过不需要普通的队列那么多的成员方法,只需要一个put和一个take,实现逻辑类比offer和poll。

首先,我们决定使用数组实现,先定义一个数组。大小自己决定。类型的话,我们这里只是为了感受阻塞队列的实现逻辑,并不需要跟源码一样,非要泛型,这里为了方便编写,我们采用int类型。

又因为是循环数组,所以需要begin和end两个指针,同时为了方便,我们采用增加一个size变量而非浪费一个空间实现唤醒数组。

其次,put方法中,我们需要首先判断队列有没有满,其次不满的话将end位置设置成指定值,end++,同时将size++,注意,这里需要对end位置合法性进行判断,如果超过长度,直接置为0.需要特别说明的是,这里的判断和重新设置end的方法,虽然取余的方法经常用,但是不是特别清晰明了,所以我们这里直接采用end>=array.length,一旦中这个逻辑直接end=0;同理take方法中,begin也是这样,只不过begin是++,size–。

然后,现在我们要考虑阻塞的功能了。对于阻塞无非就是如果满/空需要当前线程需要阻塞等待,由于调度的随机性,这个时间我们无法准确估计,那么相较于sleep,我们使用wait和notify可能更为恰当一些。

class MyBQ{
    private int[] items=new int[500];
    private int begin=0;
    private int end=0;
    private int size=0;
    public void put(int a) throws InterruptedException {
        if(size==items.length){
            this.wait();//采用声明中断异常处理这个异常
        }
        items[end++]=a;
        if(end>= items.length){
            end=0;
        }
        size++;
        this.notify();

    }
    public Integer take() throws InterruptedException {
        if(size==0){
            this.wait();
        }
        int ret=items[begin++];
        if(begin>=items.length){
            begin=0;
        }
        size--;
        this.notify();
        return ret;
    }
}

当然这里可能有人会疑问这两对wait/notify不会混吗?答案显而易见不会,因为一旦线程进入阻塞等待状态,当前的的队列要么空要么满,只可能中一个逻辑,没有其他可能。

最后,单线程下这个代码没什么问题了,那么多线程下呢??显然是有问题的。下边我们来具体分析问题在哪,并且需要怎样的方法去解决。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ArNedaEt-1673617220937)(F:\typora插图\image-20230113213302409.png)]

除此以外,多线程环境下很可能会出现wait完当前队列并不是空的或者并不是满的情况,这个时候需要加上再进行多次判断,直至符合要求,所以这里我们需要加上一个while循环进行判断。

(二)代码实现

class MyBQ{
    volatile private int[] items=new int[500];
    volatile private int begin=0;
    volatile private int end=0;
    volatile private int size=0;
    synchronized public void put(int a) throws InterruptedException {
        while(size==items.length){
            this.wait();
        }
        items[end++]=a;
        if(end>= items.length){
            end=0;
        }
        size++;
        this.notify();

    }
    synchronized public Integer take() throws InterruptedException {
        while(size==0){
            this.wait();
        }
        int ret=items[begin++];
        if(begin>=items.length){
            begin=0;
        }
        size--;
        this.notify();
        return ret;
    }
}
public class Code25_MyBlockingQueue {
    public static void main(String[] args) throws InterruptedException {
        MyBQ2 myBQ=new MyBQ2();
        myBQ.put(5);
        myBQ.put(6);
        System.out.println(myBQ.take());
        System.out.println(myBQ.take());
        Sstem.out.println(myBQ.take());
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aBAa9WNz-1673617220938)(F:\typora插图\image-20230113213534623.png)]

显然,当没有元素的时候,当前线程进入阻塞等待。

自实现阻塞队列下的生产者消费者模型

class MyBQ2 {
    volatile private int[] items=new int[500];
    volatile private int begin=0;
    volatile private int end=0;
    volatile private int size=0;
    synchronized public void put(int a) throws InterruptedException {
        while(size==items.length){
            this.wait();
        }
        items[end++]=a;
        if(end>= items.length){
            end=0;
        }
        size++;
        this.notify();

    }
    synchronized public Integer take() throws InterruptedException {
        while(size==0){
            this.wait();
        }
        int ret=items[begin++];
        if(begin>=items.length){
            begin=0;
        }
        size--;
        this.notify();
        return ret;
    }
}
public class Code26_MyBQCP {
    public static void main(String[] args) throws InterruptedException {
        MyBQ2 myBQ=new MyBQ2();
        Thread customer=new Thread(()->{
            while(true){
                try {
                    Thread.sleep(500);
                    int thing= (myBQ.take());
                    System.out.println("消费元素:"+thing);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread producer=new Thread(()->{
            int count=0;
            while(true){
                try {
                    myBQ.put(count);
                    count++;
                    System.out.println("生产元素:"+count);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        //这里顺序不用手动控制,因为是在阻塞队列中,随便的顺序都可以
        customer.start();
        producer.start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ndW09Eju-1673617220939)(F:\typora插图\image-20230113213752518.png)]

参考

ArrayBlockingQueue理解参考

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

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

相关文章

osg fbo(三),将颜色缓冲区图片通过shader变绿

这个其实很简单&#xff0c; 一&#xff0c;写顶点着色器和片元着色器 static const char * vertexShader { “void main(void)\n” “{\n” " gl_Position ftransform();\n" “}\n” }; static const char *psShader { “uniform float alpha;” “void main(vo…

12、ThingsBoard-如何配置发送邮件

1、概述 ThingsBoard提供了系统层设置邮件配置和租户层通过设置邮件规则节点,对规则引擎产生的告警进行分发这两种邮件配置,其中系统层设置邮件配置主要是针对用于向用户分发激活和密码重置电子邮件;租户层通过设置邮件规则节点是针对告警通知的;一定要区别开这两个邮件配…

SpringBoot整合SpringSecurity实现进行认证和授权。

目录 2.在子工程通过easyCode创建项目相关包和文件 3.子项目新建Controllter层&#xff0c;并建立BlogLoginController.java 4.在servic 层定义login 方法&#xff0c;并new UsernamePasswordAuthenticationToken对象&#xff0c;传入对应用户名&#xff0c;密码 5.自定义实…

Java集合(进阶)

Java集合Collection集合体系结构CollectionCollection系列集合三种遍历方式List泛型泛型类泛型方法泛型接口泛型的继承和通配符SetHashSetTreeSet总结&#xff1a;Map&#xff08;双列集合&#xff09;HashMapLinkedHashMapTreeMap可变参数集合工具类Collections集合嵌套案例不…

打破应用孤岛,iPaaS连接全域新协作

“据全球知名的咨询平台Garner分析&#xff0c;集成平台将在企业数字化转型过程中扮演重要的角色&#xff0c;企业内外应用的打通成为推动企业快速实现数字化转型的重要因素之一。SaaS 的井喷式发展也带来了新的机遇与挑战&#xff0c;企业亟需新的集成方法和手段帮助解决自身问…

吴恩达【神经网络和深度学习】Week4——深层神经网络

文章目录Deep Neural Network1、Deep L-layer Neural Network2、Forward Propagation in a Deep Network3、Getting your matrix dimensions right4、Why deep representations?5、 Building blocks of deep neural networks6、 Forward and Backward Propagation7、Parameter…

【Ctfer训练计划】——(十一)

作者名&#xff1a;Demo不是emo主页面链接&#xff1a; 主页传送门创作初心&#xff1a; 舞台再大&#xff0c;你不上台&#xff0c;永远是观众&#xff0c;没人会关心你努不努力&#xff0c;摔的痛不痛&#xff0c;他们只会看你最后站在什么位置&#xff0c;然后羡慕或鄙夷座右…

最新版wifi营销分销流量主前后端+小程序源码+搭建教程

前端后端数据库搭建教程&#xff0c;无任何密码&#xff0c;亲测能用&#xff0c;避免踩坑&#xff0c;v&#xff1a;JZ716888 教程如下&#xff1a; 安装源码到根目录 1、网站运行目录public 2、PHP7.2&#xff0c;开通SSL 3、导入数据库文件 4、修改数据库文件里applic…

【十一】Netty UDP协议栈开发

Netty UDP协议栈开发介绍协议简介伪首部UDP协议的特点开发jar依赖UDP 服务端启动类服务端业务处理类客户端启动类客户端业务处理类代码说明测试服务端打印截图&#xff1a;客户端打印截图:测试结果总结介绍 UDP 是用户数据报协议(User Datagram Protocol) 的简称&#xff0c;其…

【Azure 架构师学习笔记】-Azure Logic Apps(4)-演示2

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Logic Apps】系列。 接上文[【Azure 架构师学习笔记】-Azure Logic Apps&#xff08;3&#xff09;-演示1] (https://blog.csdn.net/DBA_Huangzj/article/details/128542539) 前言 上文做了简单的演示&#xff0c;这一…

【Flutter】关于Button 的那些知识ElevatedButton等,以及Buttonstyle

文章目录前言一、Button是什么&#xff1f;二、开始使用button1.ElevatedButton1.无style 的ElevatedButton2.基础功能的处理之后的button3.利用buttonstyle 来美化下button2.IconButton&#xff0c;TextButton基础功能都是一样的三、做几个好看点的按键总结前言 一、Button是什…

【设计模式】七大设计原则

设计模式学习之旅(二) 查看更多可关注后查看主页设计模式DayToDay专栏 在软件开发中&#xff0c;为了提高软件系统的可维护性和可复用性&#xff0c;增加软件的可扩展性和灵活性&#xff0c;程序员要尽量根据7条原则来开发程序&#xff0c;从而提高软件开发效率、节约软件开发成…

SAP 详细解析在建工程转固定资产

由固定资产归口采购部门或业务部门提交购置固定资产/在建工程的申请&#xff0c;经审批后&#xff0c;若是需要安装调试&#xff0c;则由财务部固定资产会计建立内部订单收集成本&#xff0c;月末结转在建工程。项目完工后&#xff0c;相关部门&#xff08;公司装备部、分公司装…

数据库设计之三范式

写在前面 很多数据库设计者&#xff0c;都是按照自己的性子和习惯来设计数据库数据表&#xff0c;其实不然。 其实&#xff0c;数据库的设计也有要遵循的原则。 范式&#xff0c;就是规范&#xff0c;就是指设计数据库需要&#xff08;应该&#xff09;遵循的原则。 每个范…

智慧变频中的数据监测、下发控制以及告警推送

[小 迪 导读]&#xff1a;在智能制造的推动下&#xff0c;制造商对于变频器在绿色节能、智能运行、远程维护以及大数据等方面的需求也日趋凸显。针对传统变频器无法满足智能时代的需求问题&#xff0c;dgiot可适配多种DTU/网关对变频器进行数据监测、下发控制以及告警推送。概述…

VS2019编译OSG

VS2019编译OSG 资源准备 由于3rd依赖项很多&#xff0c;编译耗时&#xff0c;可以在牛人编译的版本基础上开展。 杨石兴编译博客&#xff1b; 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/101IXFgvKQhQOEbfLa-ztZg 提取码&#xff1a;osgb 编译 1. 编译…

【patch-package】修改node_modules下的依赖包源码

场景&#xff1a;当项目里使用的element-ui有bug&#xff0c;但是项目里又急需修改这bug&#xff0c;这个时候就需要给依赖打补丁啦~ 1、patch-package 1.1、概念 lets app authors instantly make and keep fixes to npm dependencies. Its a vital band-aid for those of u…

【hcip】mpls实验

目录 1.拓扑图 2.要求 3.主要配置 4.测试 1.拓扑图 2.要求 实现全网可达 3.主要配置 isp区域已配置ospf&#xff0c;bgp 然后配置mpls&#xff08;r2&#xff09; r2]mpls lsr-id 2.2.2.2 [r2]mpls Info: Mpls starting, please wait... OK! [r2-mpls]mpls ld [r2-mpls…

VTK-vtkPolyData解读

小结&#xff1a;本博文主要讲解vtkPolyData接口及常用的方法实现原理。 vtkPolyData 1描述 vtkPolyData是一系列的数据集包括vertices&#xff0c;lines&#xff0c;polygons&#xff0c;triangle strips。 vtkPolyData是vtkDataSet的具体实现&#xff0c;代表了一系列的几…

ELF文件格式解析

ELF文件是什么&#xff1f; ELF是Executable and Linkable Format的缩写&#xff0c;字面上看就是可执行和可连接文件。在Linux下可重定位文件(.o)、可执行文件、共享目标文件(.so)、核心转储问文件(core dump) 都是使用ELF文件格式。 ELF 通常由编译器或者连接器产生&#x…