javaEE-线程的常用方法-4

news2024/12/23 5:42:03

目录

一.start():启动一个线程

调用start()方法

start()方法只能调用一次:

java中的API:

start()和run()的区别:

二.中断一个线程

中断线程方法1:引入标志位

中断线程方法2:调⽤interrupt()⽅法

抛出的异常:

三.等待一个线程 join()

四、获取线程引用

五。线程的状态

六、线程安全(重点,难点)

引起线程不安全的原因:

解决方法:

“加锁”

“可重入”性

死锁

java标准库中的线程安全类:

内存可见性



一.start():启动一个线程

之前我们已经看到了如何通过重写run⽅法创建⼀个线程对象但线程对象被创建出来并不意味着线程就开始运⾏了。重写run⽅法是提供给线程要做的事情的指令清单.

调用start()方法

调⽤start⽅法,才真的在操作系统的底层创建出⼀个线程.

start()方法只能调用一次:

对于同一个线程对象来说,start()方法只能调用一次.

若调用多次,除第一次的调用,之后的线程就会出现 illegalThreadStateException(非法线程状态异常)异常终止,而第一次调用的线程还能正常运行.

要想启动更多的线程,就要创建新的线程对象.

这里的main线程,和两个t1,t2 线程都是每隔一秒执行一次,可以看出打印结果 main和thread也是正如分析的一样.

在jconsloe.exe上也能看到三个线程的执行.

java中的API:

API 是应用程序编程接口(Application Programming Interface)的缩写。

 API 的结构:

在 Java 中,API(应用程序编程接口)指的是整个包、接口、类、方法等以及它们之间的关系和规范。API 是开发者用来构建应用程序的工具集,它包括了所有这些元素.

Java API ,指的是整个 Java 标准库,包括所有的包、接口、类、方法和异常等。这些元素共同构成了 Java 语言的核心功能,使得开发者能够构建各种应用程序,从简单的命令行工具到复杂的企业级应用。

它是一套预定义的函数、方法或类的集合,允许应用程序访问某些功能或数据,而无需关心底层的实现细节。API 为开发者提供了构建软件应用的积木。

start()和run()的区别:

调用start()是创建了一个新的线程,main线程,和t线程,两个线程同时工作.互不干扰.(双分支)

而若单调用run()方法,就只是在main()线程中,去执行了一个run()方法,不存在多线程等问题,属于串行执行。

此处.调用run()方法后,就只能去执行run()方法中的代码,而main()方法调用run()方法后面的代码,只有执行完run()方法后,才能去执行.但此处的run()方法体内是死循环,后面的打印mainjiu无法执行(属于一条路线).

二.中断一个线程

一个线程在执行过程中,因某些需要,要让该线程中断,不再执行.就需要对该线程进行中断处理。

(就好像你在打游戏,突然来了个电话,就要先中断你的游戏,去接听电话)

中断线程方法1:引入标志位

通过共享的标记来进⾏沟通(这需要线程之间的代码逻辑的配合执行)

设置静态变量,通过对其修改,来实现中断线程的功能

注意:这里的isQuite是设置在全局变量处的,而不能设置在main线程中,

原因是run()方法是通过使用lambda表达式(匿名内部类)来实现的,但lambda函数中的变量要遵循变量捕获原则,就是内部用到的局部变量不能是可以修改的,而此处的isQuite又需要对其修改,因此不能设置成fianl类型的。

但lambda表达式可以访问到方法外定义的任意变量,因此, 就只能设置成全局变量了.

lambda表达式中,不允许存在可能被修改的变量的原因是:

这里结果,执行完"3s后 Thread线程结束",在将isQuite设置为true之前,又执行了一次t线程,才结束t线程.

这里执行完"3s后 Thread线程结束",直接将isQuite设置为true,结束t线程。

可见线程的执行顺序和执行时间是随机的.

中断线程方法2:调⽤interrupt()⽅法

isInterrupted():判定标志位

interrupt():设置标志位

将run方法内的循环条件设置为判定标志位,再在调用标志位,使其改变,达到中断线程的效果。

但这样在执行的时候会抛出一个异常

在sleep()函数,当主动让t线程结束(修改interrupted标志位)的时候,此时sleep()的执行还未结束,当sleep()被提前唤醒的时候,会自动清除interrupted标志位.就会出现矛盾:到底是让该线程结束,还是继续执行.

要不想让异常终止,只需要修改异常内容就可以.

抛出的异常:

旧版的idea是执行try-catah后,catch里的代码是自动打印调用栈.

新版的idea是执行try-catah后,catch中再抛出一个异常.

但是在实际开发过程中,catch对以上两种方法都不用,idea生成的这两种方法都不用,这只是一个站位的作用.

在实际开发中,catch代码块中实际可能会进行如下操作:

在java中.程序的终止,是一种"软性"操作.就是说,需要线程中的代码配合,才能达到中断的效果.

三.等待一个线程 join()

因为多线程是随机调度的,有时,我们需要等待⼀个线程完成它的⼯作后,才能进⾏⾃⼰的下⼀步⼯作。

为了实现这种效果,该方法就能解决这样的问题的。等待一个线程,指的是让一个线程执行结束,再进行之后的执行.

多用于一个线程不确定执行时间,且要等待该线程结束,再进行别的线程操作.

下面的代码实现这样的功能:在t线程中执行1到5的相加运算,再在main线程中将结果打印

package Thread_;

public class Thread11 {
    private static int count;
    public static void main(String[] args) throws InterruptedException {
        //计算1--5相加,再在main线程中打印结果
        Thread t=new Thread(()->{
            for(int i=0;i<5;i++){
                count+=i;
            }
        System.out.println("Thread线程执行结束!");
        });
     t.start();
     t.join();//线程等待
     System.out.println("结果为: "+count);
    }
}

join()的功能:在哪个线程中调用被调用,就暂停该线程(进入阻塞状态),哪个线程调用该方法,就先执行哪个线程.

join()方法有一个受查异常InterruptedException,使用时需要处理。

上面的代码中,join方法在main线程中被调用,则main就进入阻塞状态。t线程调用了该方法,则要等t线程执行结束,才继续执行main线程。(就是说:main线程要等t线程执行结束之后,main才能继续执行)

就是因为阻塞,使这两个线程结束产生了先后关系。

//计算1--10000相加,分成两个线程执行,再在main线程中打印结果
private static int count;

    public static void main(String[] args) throws InterruptedException {
        //计算1--10000相加,分成两个线程执行,再在main线程中打印结果
        Thread t1=new Thread(()->{
            int n=0;
            //t1: 计算1到5000的相加
            for(int i=1;i<=5000;i++){
                n+=i;
            }
            count+=n;
            System.out.println("Thread线程执行结束!");
        });
        Thread t2=new Thread(()->{
            int n=0;
            for(int i=5001;i<=10000;i++){
                n+=i;
            }
            count+=n;
            System.out.println("Thread线程执行结束!");
        });
        t1.start();
        t2.start();
        
        t1.join();//线程等待
        t2.join();//线程等待
        System.out.println("结果为: "+count);
    }

此时的结果是正确的,但是若进行更大数字的相加时,

让其计算1到100亿数字的相加,算一下执行时间。

一个线程完成计算:

 private static long count;

    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
            for(long i=0;i<100_0000_0000L;i++){
                count+=i;
            }
        });
        t.start();
        long beg=System.currentTimeMillis();
        t.join();
        long end=System.currentTimeMillis();
        System.out.println("count= "+count);
        System.out.println("运算时间为: "+(end-beg));

    }

运行时间为3074ms.

 两个线程完成计算:t1负责完成前50亿的计算,t2负责完成后50亿的计算。

private static long count;

    public static void main(String[] args) throws InterruptedException {
        //计算1--100_0000_0000(100亿)相加,分成两个线程执行,再在main线程中打印结果
        Thread t1=new Thread(()->{
            long n=0;
            //t1: 计算1到5000的相加
            for(long i=1;i<=50_0000_0000L;i++){
                n+=i;
            }
            count+=n;
            System.out.println("Thread线程执行结束!");
        });
        Thread t2=new Thread(()->{
            long n=0;
            for(long i=50_0000_0001L;i<=100_0000_0000L;i++){
                n+=i;
            }
            count+=n;
            System.out.println("Thread线程执行结束!");
        });
        t1.start();
        t2.start();

        t1.join();//线程等待
        t2.join();//线程等待
        System.out.println("结果为: "+count);
    }

 

运行时间为1827ms.

双线程运算时间缩短,但此时也会存在线程安全的问题.

join()方法还有别的重载方法:

join():属于死等,t2线程不结束,就不会向下执行,

join(long millis):超时等待。在millis时间内,若t2线程没有结束,就不再等待,进行正常的代码流程。

第三个是设置一个ns级的时间,过于精确,用处不大。

要想把被join()阻塞的进程提前唤醒,也是可以通过interrupt()方法,将其唤醒。

四、获取线程引用

Thread.currenThread():获取当前线程的引用(Thread的引用)

如果是继承Thread,则可直接是由this调用;

若是实现Runnable接口或lambda表达式,此时this就不能代替Thread了,只能使用Thread.currendThread().

五。线程的状态

就绪状态:线程正在执行,或者随时准备着CPU的调用,执行的状态。

阻塞状态:线程暂时不方便去CPU上执行。

java中,线程有以下这几种状态:

1、NEW:Thread线程创建好了,但是还未调用start()方法。且直有处于NEW状态的线程才能调用start().

2、TERMINATED:Thread对象仍然存在,但是该线程已经执行完毕

3、RUNNABLE:就绪状态,线程正在执行,或者随时准备着CPU的调用,去CPU上执行。

4、TIMED_WAITING:指定时间的阻塞状态,达到一定时间后,自动解除阻塞

5、WAITING无时间限制的阻塞(死等),直有满足指定条件,才会结束阻塞。(join()/wain()都会进入WAITING状态)

6、BLOCK:由于锁竞争引起的阻塞。(存在线程安全的问题)

各状态的转换关系:

了解这些状态后,对代码的调试起到非常大的帮助

在jconsloe.exe中,也能看到线程的状态:

六、线程安全(重点,难点)

某个代码,若不论是在单线程下执行,还是在多线程下执行,都不会出现bug,这样的线程称为“线程安全”

若在单线程下运行正确,在多线程下,就可能产生bug,这样的线程就是称为“线程不安全”的,或叫存在“线程安全”问题

1.用一个线程计算1到10000的和,main线程打印结果:

结果正确.

2.用两个线程 t1计算1-5000的和,t2计算5001-10000的和,main打印结果:

该方法运行几次,发现每次的执行结果不确定,并且结果还是错误的。这就属于存在“线程安全问题”的代码。

这里的count++,在系统的底层其实是执行的三个cpu指令

1、load:从内存读取数据到cpu寄存器上。

2、add:将寄存器中的值+1.

3、save:将寄存器中的值写回内存中。

两个线程执行的三个cpu指令可能有各种顺序。

列出几种情况:

但是无数种情况中,只有在一个线程从load到save执行完毕后,再去执行下一个线程的load,才能得到正确结果。

在5万次的自增过程中,也不知道多少次是正确的执行顺序.这也是为啥采用两个线程计算时,每次的结果不但错误,且不一样.

引起线程不安全的原因:

1.操作系统上的线程是“抢占式线程”,“随即调度”的。这给线程之间的执行顺序带来了很多变数。(根本原因)

2、代码结构上:代码中存在多个线程同时修改一个变量

(一个线程修改一个变量,或多个线程读取一个变量,或是多个线程修改多个变量,这些都不会引起线程安全问题)

3.上面的线程修改操作(load->add->save),不是“原子的”操作(要莫不执行,要么执行完)(直接原因)

不是“原子的“指的是,一个线程上的这些指令,执行到一半,可能会被调度走,让其他线程继续执行。而每个cpu指令(load,add,save....单个来看)都是原子的(要不不执行,要不执行完)。

4、内存可见性问题。

5、指令重排序问题。

解决方法:

1、针对线程的“抢占式线程”,“随即调度”。

2.代码结构上:可以不让多个线程同时修改一个变量,但这个要分情况,有时可以调整,但有时是无法实现调整的。

3、不是“原子的”操作:可以将count++生成的几个指令,通过一些方法,将其打包,使其成为一个“整体”。

“加锁”

可以通过“加锁”,来实现这样的效果。锁具有“互斥”,“排他”这样的特性。

在java中,加锁的方式有好多种,最主要使用的方式是通过加synchronized关键字,来加锁。

加锁的目的是,把count++的三个操作(load,add,save)打包成一个原子操作。

但这里进行锁操作,需要先准备一个“锁对象”,加锁,解锁的操作都是依托锁对象来执行的。

synchronized(对象){ }

进入{}后,会进行加锁(lock),出{}后,进行解锁(unlock)。

在java中,任何一个对象都可以成为锁对象。也就是说()中的内容可以是随意的,但必须为对象。

如果一个线程,针对一个对象加上锁之后,若别的线程也想对这个对象上锁,该线程就会产生阻塞(BLOCKED),直到上一个线程解锁为止,该线程才能继续操作。

解析:每次count++之前,进行上锁,count++之后,进行解锁。

若两个对象针对不同的对象加锁,则就不会有锁竞争,也不会产生阻塞。此时还是会存在线程安全问题。

这里能否通过上锁解决线程安全问题,最主要的就是是否对同一个对象上锁

synchronized是调用系统的api进行加锁的,系统的api本质上是靠cpu上的特定指令完成加锁的。

通过锁竞争,让第二个线程的指令无法插入到第一个线程的执行指令之间,而不是禁止第一个线程被调度出cpu.

若一个线程加锁,另一个线程不加锁,又会怎样呢?

通过结果可以看出,仅对一个线程加锁,是无法解决线程安全问题的。未加锁的线程中的count++操作,仍然会被另一个线程插队。

synhrionzed的别的加锁方式:

class Test{
    public int count1;
    synchronized  public void add(){
        count1++;
    }
}
public class Thread13 {
    //线程安全问题
    public static void main(String[] args) throws InterruptedException {
//        Object block = new Object();
        Test test = new Test();
        Thread t1=new Thread(()->{
            for(int i=1;i<=10000;i++){
                test.add();//将count++放到类的方法中,对该类进行加锁
            }
        });
        Thread t2=new Thread(()->{
            for(int i=1;i<=10000;i++){
                test.add();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count="+test.count1);
    }
}

或者是以这种形式:

这都是对同一个对象加锁,这等价于将锁加到方法上。类似于上面的代码

若是synchronized加到静态方法上,则相当于对类进行了加锁

后两个原因之后再解释。

关于String:

“可重入”性

若加两层锁,会怎样呢?

感觉这样写,会进入一个死等的状态,但结果:

正常输出!!!

这又是什么原因呢?

是因为加的两次锁,是在同一个线程中进行的,在第二次加锁的时候,知道该线程已经加过锁了,就不会进行阻塞,会继续执行代码,这个特性称为“可重入”

使用“可重入”性,就会避免类似上面的代码出现阻塞状态。

如果没有“可重入”性,当写的代码非常复杂时,就非常容易出现这样的阻塞状态。因为加锁的代码可能会非常隐蔽。

底层内部实现可重入行:

有一个计数器,最初为0,在第一次加锁的时候,计数器+1,同时记录是哪个线程加的锁;

在第二次加锁的时候,此时计数器为1,判定持有锁线程和加锁线程是否为同一个线程,若为同一个线程,说明该线程已经加过锁了,就不再加锁了,仅对计数器+1;若不为同一线程,则加锁线程就会进入阻塞状态。

解锁的时候,是从内层向外层以此解锁,每到 },计数器就-1,当计数器为0,就真正实现解锁了。

注意:整个过程只有一把锁,

死锁

加锁能够解决线程安全问题,但是若加锁处理不当,就可能产生死锁。

产生死锁的四个必要条件(全部具备,才会产生死锁):

1、互斥使用:一个线程获取到了这把锁,另一个线程也想获取这把锁,就进入了阻塞状态。

2、不可抢占:一个线程拿到这把锁之后,只能主动解锁,别的线程不能强行把锁抢走。

3、请求和保持:一个线程获取锁A之后,尝试获取锁B.

4、循环等待:该线程尝试获取锁,进入阻塞状态,未获取到,就一直处于阻塞状态。

死锁的三种场景:

1、一个线程,一把锁:就像上面的在一个线程内,两次获取同一把锁,若没有可重入性,则该线程就会进入死锁状态。

2、两个线程,两把锁:线程1获取到了锁A,线程2获取到了锁B;接下来,线程1尝试获取锁B,线程2尝试获取锁A。两个线程都不能获取到,都进入了阻塞状态,就产生了死锁。

3.M各线程N把锁:

最经典的问题:哲学家就餐问题:假设5个哲学家就餐,但直有5根筷子,

针对上述问题,解决死锁,有几种方法:

1、加一个筷子(加一把锁)。

2、减少一个哲学家(减少一个线程)。

3、让线程获取琐时,按规定顺序获取。(给锁编一个号,让线程从小到大获取锁)(这种方法比较常用)

4、银行家算法。(比较复杂,先不讨论)

java标准库中的线程安全类:

Java标准库中很多都是线程不安全的.这些类可能会涉及到多线程修改共享数据,⼜没有任何加锁措 施:

这些在java标准库中,都准备弃用了。

内存可见性

如果一个线程写,一个线程读,是否会引起线程安全问题呢?

在t1线程中,设置循环条件:判断flag是否被修改,在mian线程中通过控制台输入,修改flag:

输入1后,t1线程并没有按照预期结果结束执行!

说明引起了线程安全问题。

这是因为和内存可见性问题有关。

在t1线程的while循环中会执行两条核心指令:

1、load:读取内存中flag的值到cpu寄存器上。

2.字节跳转指令:将cup寄存器上的值和0进行比较。

在用户输入值之前,t1线程已经进行过多次循环了(上亿次),其中load每次从内存中读取的值都是相同的,并且load操作的开销远超过字节跳转(访问寄存器的速度远远超过访问内存)

这之后,再修改flag的值,就没有作用了。

想让自己写的代码,无论什么情况都不会出现内存可见性问题,可以用volatile关键字来修饰,这样就可以使上述优化强制关闭,保证每次循环都是从内存中读取数据的。(同时,也降低了代码执行的效率)

引入volatile关键字,相当于把主动权交给了程序员自己。

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

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

相关文章

uniapp小案例---趣味打字坤

当点击输入框时出现小鸡打字 当输入框失焦时打字鸡沉下去 原图自取 这里运用了一个三元 :class"isActive?active:"&#xff0c;当聚焦时isActivetrue从而让class绑定&#xff0c;当失焦时isActivefalse <template><view class"out"><inp…

JS使用random随机数实现简单的四则算数验证

1.效果图 2.代码实现 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</ti…

vue3项目结合Echarts实现甘特图(可拖拽、选中等操作)

效果图&#xff1a; 图一&#xff1a;选中操作 图二&#xff1a;上下左右拖拽操作 本案例在echarts​​​​​​​示例机场航班甘特图的基础上修改​​​​​​​ 封装ganttEcharts组件&#xff0c;测试数据 airport-schedule.jsonganttEcharts代码: 直接复制粘贴可测​​​​…

【已解决】黑马点评项目jmeter高并发测试中用户数据的生成

具体实现见此篇文章的第3章 运行 test 程序后&#xff0c;生成以下用户名 以下文件名改成自己的地址 成功

VScode 查看linux 内核代码

0&#xff0c;安装c.c 1&#xff0c;查看linux 目录下的linux代码&#xff0c;安装remote ssh 2&#xff0c; 输入服务器IP 3 选择服务器为linux

【游戏设计原理】21 - 解谜游戏的设计

你想象一下&#xff0c;刚坐下准备玩游戏&#xff0c;想着“今天得挑战一下我的智商极限&#xff01;”可结果碰上一个谜题&#xff0c;傻眼了&#xff0c;心里默念&#xff1a;“这啥玩意儿&#xff1f;这游戏是在玩我吗&#xff1f;”如果这个谜题太简单了&#xff0c;你可能…

解析交通事故报告:利用 PDF、AI 与数据标准化技术构建智能分析系统

在交通事故处理中&#xff0c;数据的准确性与完整性至关重要。传统上&#xff0c;交通事故报告通常以 PDF 格式呈现&#xff0c;这使得手动提取数据成为一项繁琐且容易出错的任务。随着人工智能与数据处理技术的发展&#xff0c;如何自动化这一过程并提升数据质量&#xff0c;成…

基于Python+Vue开发的体育用品商城管理系统,实习作品,期末作业

项目简介 该项目是基于PythonVue开发的体育用品商城管理系统&#xff08;前后端分离&#xff09;&#xff0c;这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能&#xff0c;同时锻炼他们的项目设计与开发能力。通过学习基于Python的体…

7.C语言 宏(Macro) 宏定义,宏函数

目录 宏定义 宏函数 1.注释事项 2.注意事项 宏(Macro)用法 常量定义 简单函数实现 类型检查 条件编译 宏函数计算参数个数 宏定义进行类型转换 宏定义进行位操作 宏定义进行断言 总结 宏定义 #include "stdio.h" #include "string.h" #incl…

【LeetCode】906、超级回文数

【LeetCode】906、超级回文数 文章目录 一、通过数据量猜解法 枚举 数学 回文1.1 通过数据量猜解法 枚举 数学 回文1.2 多语言解法 二、打表法 一、通过数据量猜解法 枚举 数学 回文 1.1 通过数据量猜解法 枚举 数学 回文 减小数据规模: 先构成回文, 再平方, 再判断是否是范围…

SpringBoot的创建方式

SpringBoot创建的五种方式 1.通过Springboot官网链接下载 注意SpringBoot项目的封装方式默认为Jar 需要查看一下&#xff0c;自己的Maven版本是否正确 创建成功 2.通过 aliyun官网链接下载 修改服务路径为阿里云链接 创建成功 3.通过Springboot官网下载 点击&#xff0c;拉到最…

Android Studio AI助手---Gemini

从金丝雀频道下载最新版 Android Studio&#xff0c;以利用所有这些新功能&#xff0c;并继续阅读以了解新增内容。 Gemini 现在可以编写、重构和记录 Android 代码 Gemini 不仅仅是提供指导。它可以编辑您的代码&#xff0c;帮助您快速从原型转向实现&#xff0c;实现常见的…

物理信息神经网络(PINN)八课时教案

物理信息神经网络&#xff08;PINN&#xff09;八课时教案 第一课&#xff1a;物理信息神经网络概述 1.1 PINN的定义与背景 物理信息神经网络&#xff08;Physics-Informed Neural Networks&#xff0c;简称PINN&#xff09;是一种将物理定律融入神经网络训练过程中的先进方…

双臂机器人

目录 一、双臂机器人简介 二、双臂机器人系统的组成 三、双臂机器人面临的主要挑战 3.1 协调与协同控制问题 3.2 力控制与柔顺性问题 3.3 路径规划与轨迹优化问题 3.4 感知与环境交互 3.5 人机协作问题 3.6 能源与效率问题 3.7 稳定性与可靠性问题 四、双臂机器人…

日期区间选择器插件的操作流程

我们知道&#xff0c;在开发过程中&#xff0c;为了能够在规定时间内完成项目&#xff0c;有时候我们都会使用插件来大大提高我们的开发效率&#xff0c;有些插件是可以直接拿来用&#xff0c;但是有些插件拿过来之后是需要进行修改&#xff0c;在使用插件的时候还有很多的注意…

以ATTCK为例构建网络安全知识图

ATT&CK&#xff08;Adversarial Tactics, Techniques, and Common Knowledge &#xff09;是一个攻击行为知识库和模型&#xff0c;主要应用于评估攻防能力覆盖、APT情报分析、威胁狩猎及攻击模拟等领域。本文简单介绍ATT&CK相关的背景概念&#xff0c;并探讨通过ATT&a…

“年轻科技旗舰”爱玛A7 Plus正式发布,全国售价4999元

12月18日&#xff0c;备受行业瞩目的“A7上场 一路超神”爱玛旗舰新品发布会在爱玛台州智造工厂盛大举行。 作为年末“压轴产品”的“两轮豪华轿跑”爱玛A7Plus重磅上场&#xff0c;以“快、稳、帅、炫、智、爽”六大超神技惊艳四座&#xff0c;不仅践行了爱玛科技的精品战略&…

精通Redis(一)

目录 1.NoSQL 非关系型数据库 2.Redis 3.Redis的java客户端 4.Jedis 4.1Jedis快速入门 4.2Jedis连接池及使用 5.SpringDataRedis和RedisTemplate 1.NoSQL 非关系型数据库 基础篇-02.初始Redis-认识NoSQL_哔哩哔哩_bilibili NoSQL与SQL的区别就在于SQL是结构化的、关联…

研发效能DevOps: Vite 使用 Element Plus

目录 一、实验 1.环境 2.初始化前端项目 3.安装 vue-route 4.安装 pinia 5.安装 axios 6.安装 Element Plus 7.gitee创建工程 8. 配置路由映射 9.Vite 使用 Element Plus 二、问题 1.README.md 文档推送到gitee未自动换行 2.访问login页面显示空白 3.表单输入账户…

openbmc hwmon与sensor监控

1.说明 参考文档: https://github.com/openbmc/entity-manager/blob/master/docs/entity_manager_dbus_api.mdhttps://github.com/openbmc/entity-manager/blob/master/docs/my_first_sensors.md 1.1 简单介绍 注意: 本节是快速浏览整个sensor框架&#xff0c;了解大致open…