《JavaEE》----2.<多线程的简介创建Thread类>

news2024/11/22 14:45:25

前言:

      大家好,我目前在学习java。我准备利用这个暑假,来复习之前学过的内容,并整理好之前写过的博客进行发布。如果博客中有错误或者没有读懂的地方。热烈欢迎大家在评论区进行讨论!!!

      喜欢我文章的兄弟姐妹们可以点赞,收藏和评论。如果感觉有所收获可以关注我呦。我会持续更新滴,望支持!!!!!!一起加油呀!!!!

语言只是工具,决定你好不好找工作的是你的能力!!!!!

学历本科及以上就够用了!!!!!!!!!!


本篇博客会简单介绍线程、线程的特点、优点、线程的不安全问题、进程与线程的区别、Java中如何进程多线程编程、Thread类。

c++中会讲很多多进程编程,而在Java这样的生态中,并不是很鼓励多进程编程,更鼓励多线程编程。

引入多个进程,目的是为了实现并发编程=>多核cpu的时代。

多进程实现并发编程效果很好,但是多进程编程模型也有明显的缺点:

多进程编程模型的缺点

进程太重量,效率不高。

创建一个进程,销毁一个进程,调度一个进程消耗时间都比较多。

时间消耗在申请资源上。进程是资源分配的基本单位。分配内存操作是一个复杂的操作。

(操作系统内部有一定的数据结构,把空闲的内存分块管理好,当我们进行申请内存的时候,系统就会从这样的数据结构中找到一个大小适合的空闲内存。返回给对应的进程。这里虽然通过此处的数据结构,可以一定程度提高效率,整体来说,管理的空间比较多,相比之下,还是一个耗时操作。)

如果频繁创建/销毁进程时,这个耗时就不能忽视了

为了解决上述问题,就引入了“线程”(Thread)

一、线程也叫做“轻量级进程”

线程不能独立存在,而是要依附于进程,(进程包含线程,可以包含一个或多个)

一个进程最开始至少要有一个线程,这个线程负责完成执行代码的工作。

也可以根据需要,创建出更多的线程,从而使当前实现“并发编程”的效果

每个线程都可以独立执行一些代码。

之前提到的进程调度

是基于“一个进程里只有一个线程”的情况。

实际上,一个进程中,是可以有多个线程的~~每个线程都是可以独立的进行调度的~~

因此以后看到进程的调度,我们就知道,并不是把整个进程进行调度。而是去调度进程里面的每一个线程,每一个线程执行一些逻辑,每一个线程就可以分别在这上面进行调度。每一个线程也有

状态

优先级

上下文

记账信息....

一个进程,可能使用一个PCB表示,也可能使用多个PCB表示,每一个PCB对应到一个线程上,因此每一个线程都有自己的状态、优先级、上下文、记账信息....每一个线程都有这些信息进行辅助调度~~

除此之外,前面谈到的pid,是相同的。内存指针,文件描述符表也是相同的。共用同一份的

线程和线程之间共用同一份pid、内存指针、文件描述符表

二、线程的特点

1.每一个线程都可以独立的去cpu上调度执行

2.同一个进程的多个线程之间共用同一份内存空间和文件资源...

三、线程的优点

创建效率更高

(创建线程的时候,不需要重新申请资源了,直接复用之前已经分配给进程的资源,省去了资源分配的开销,于是创建效率就更高了)

总结:

进程中包含线程,

一个进程由多个PCB共同表示,

每个PCB就用来表示一个线程,

每个线程都有自己的状态、优先级、上下文、记账信息....

每个线程都可以独立去CPU上调度执行,

这些PCB共用了同样的pid、内存指针、文件描述符表

创建线程(PCB)的时候,不需要重新申请资源了,直接复用之前已经分配给进程的资源,省去了资源分配的开销,于是创建效率就更高了

进程是资源分配的基本单位

线程是调度执行的基本单位

一个系统中,可以有很多进程,每个进程都有自己的资源

一个进程中,可以有很多线程,每个线程都能独立调度,共享内存/硬盘资源

四、多线程方式

刚开始创建第一个线程的时候,相当于和进程一起创建,还是需要有一定的开销去申请资源的(这个账是记在进程上面的)后面再创建线程,开销就省下了。

创建多线程方式可以提高效率,但是线程到达一定的数量,效率就无法进一步提升了,反而会因为需要调度的线程太多,使调度的开销更大,反而降低效率。线程多了,也容易产生一定的冲突。

五、线程不安全问题

如果一个线程抛出异常,如果没有妥善处理(要catch住),就容易使整个进程崩溃。此时其他线程也会随之消亡。

六、进程与线程的区别(经典面试题)

1.进程包含线程,一个进程里面可以有一个线程,也可以有多个线程

2.进程和线程,都是用来实现 并发编程 场景的,但是线程比进程更加轻量,更高效

3.同一个进程的线程之间,共用同一份的资源(内存+硬盘),省去了申请资源的开销

4.进程和进程之间,具有独立性,一个进程挂了,不会影响其他进程

   同一个进程的线程和线程之间,可能互相影响,(线程安全问题+线程出现异常)

5.进程是资源分配的基本单位,线程是调度执行的基本单位。

七、Java如何进行多线程编程 

7.1 基本的多线程编程

创建线程的方法

①继承Thread类,重写run方法

线程是操作系统的概念,操作系统提高了一些API,可以操作线程Java针对上述系统API进行了封装(实现跨平台)程序员只需要掌握这一套API就可以了。

Thread类,创建Thread类对象,进一步的就可以操作,系统内部的线程了。使用这个类,创建出一个线程出来。继承“Thread”是Java标准库内置的类,我们直接就能使用。

此处Thread不需要import也能使用,是因为Thread这个类在java.lang包下。

1.创建一个类继承Thread。再重写run方法。这个run方法就是线程的入口方法。入口方法就是代表线程一旦执行起来后,具体要执行哪些逻辑。类似于main方法。

:每个线程都是独立的执行流,每个线程都可以执行一系列的逻辑(代码)一个线程跑起来,就是从它的入口方法开始执行。

类比运行Java程序:就是跑起来一个java进程,这个进程里面至少会有一个线程,主线程的入口方法就是main方法。

2.2.创建一个主线程,也就是在main方法中创建一个Thread的实例,创建好了之后再去调用start方法。

start和run方法的区别的功能描述

//start和run都是Thread的成员

//run只是描述了线程的入口(线程要做什么任务)

//start则是真正调用了系统API,在系统中创建出线程,让线程再调用run

这里的创建线程,实在系统内核里面创建线程。涉及到创建PCB并且加入到内核链表里面,这样创建好的线程就会进一步执行我们的run方法。这样就可以将新的线程创建出来。

此时若在run中有System.out.println(“hello thread”) ;

那么运行程序,就会打印出hello thread


给打印代码加上while(true),死循环,在线程和主线程中一个打印hello thread,另一个打印hello main。运行代码,我们可以发现两边的日志都在交替打印

1.每个线程都是独立执行的逻辑,独立的执行流。

2.从t.start();代码之后,就会兵分两路,并发执行。达到了并发编程的效果,充分的使用了多核cpu资源。


把t.start()改成t.run()。并不会创建新的线程,只有一个主线程,这个主线程依次执行循环,执行完一个循环再执行另一个。

main这个线程是jvm自动创建的,和其他线程相比,没啥特殊的。

一个Java进程中,至少会有一个main线程。

 7.2 查看该进程里的多线程情况

多线程程序运行的时候,可以使用IDEA或者jconsole来观察到该进程里的多线程的情况

IDEA对新手不太友好,以调试模式启动程序,会有一个专门的窗口,查看方法的调用栈,在这里可以看到所有线程的信息。

jconsole来观察到该进程里的多线程的情况(jdk的bin目录中)

1.启动之前,确保idea中的程序已经跑起来了

2.若啥都不显示,可能需要使用管理员方式运行

它会列出当前机器上所运行的所有java进程

使用方法:

1.双击jconsole.exe,出现如下窗口,点击本地进程中你正用IDEA跑起来的进程。点击连接

 2.出现如下窗口,我们要查看线程情况,点击线程

在jconsole,可以看到一个java进程,即使是最简单的,里面也包含了很多线程。

Thread = new MyThread();是自己动手创建的,其他的线程都是JVM自动创建的。一个java进程,启动之后JVM会在后面,默默帮我们做很多事情,比如垃圾回收、资源统计、远程方法调用...

我们只关注两个

1.main是主线程

2.Thread-0是我们创建的线程 ,点进去我们可以看到详细信息。

最主要,我们看堆栈跟踪(也就是线程的“调用栈”):描述了方法的调用关系。

功能:

未来写一些多线程程序的时候,就可以借助这个功能看到该程序实时的运行情况,比如你写的程序“卡死了”。

让while循环慢点(sleep)

在循环体里加上sleep,休眠

Thread.sleep();

输入参数的单位为毫秒。例如:

Thread.sleep(millis:1000);

此语句需要抛出异常,要么往上throws或者进行try,catch。

在当前我们写的线程中(重写run方法),我们必须进行try,catch,因为我们现在是方法重写,如果父类的run方法没有throws,那么子类这个方法也就没法去throws。

而在主线程(main方法中),我们可以进行throws。

sleep是Thread的类(静态)方法。

我们发现两线程都是休眠1000ms,当时间到了之后,这俩线程谁先执行,谁后执行不一定。这个过程可以视为“随机的”。

“对多线程调度顺序的“随机性””

因为操作系统对于多个线程的调度顺序,是不确定的,“随机的”(此处的随机不均等,可能优先级不一样,就算一样是不是均等也很难说,取决于操作系统对于线程调度的模块,调度器的实现),

类方法,类属性 VS(普通) 实例方法,实例属性 

类方法,类属性,直接用类名就可以调用

实例方法,实例属性,需要用类实例化对象,用对象名进行调用。

ps:static历史问题

c语言最初引入了static,以前的操作系统,运行的进程中,专门有一个内存区域,叫做“静态内存区”随着时间发展,静态内存区没有了,后来c就是用static表示其他含义了。

如果static修饰一个全局变量,或者修饰一个方法,表示它的作用域,就在当前.c文件里

如果修饰一个局部变量,那么就表示这个变量的生命周期是跟随整个程序的。

c++中把static又赋予了新的含义,c++引入了类和对象的概念,static就是类的成员,达到“类方法”“类属性”定义效果了(如果新加关键字来表示“类方法”“类属性”不合适,这会导致现有的代码可能产生冲突。如与之前变量名一样,c++要考虑和c兼容,有很多程序使用c,若引入关键字,可能导致现有的代码无法编译。)

java是从c++这边参考过来的,因此java这边也就用static表示类方法,类属性

Python没有这样的历史包袱,因此python直接使用@classmethod这样的方式来表示类方法。

7.3 创建线程的其他写法

②实现Runnable接口,重写run方法 

实现Runnable接口,重写run方法 

这里的内容和之前继承Thread类是一样的。也是描述了线程的入口。

不同的是,这里在main方法中,需要创建Runnable接口的实例化,描述一个任务。

再创建Thread的类的实例化,将Runnable的实例化交给Thread来执行。我们把这个任务放到线程里面去执行。通过t.start();通过这个操作,调用系统api来完成创建线程的工作~

Runnable 

Runnable本身并没有和线程进行联系,单纯的表示一个可运行的任务,这个任务是交给线程负责执行,还是交给其他实体来执行....Runnable本身并不关心。

ps:为什么总是向上转型(java)

Java这个圈子就爱这么写,

如果c++,这里的代码绝对不会写成向上转型。

c++是一个生态,这里的这群人不喜欢向上转型,他们觉得向上转型之后,触发多态,会有额外的运行时开销,不符合c++把性能追求到极致这样的初心。这边能不向上转型就不转型。

如果java,一定写成向上转型的方法。

java也是一个生态。这群人更鼓励使用向上转型,java程序员觉得性能问题不是问题,开发效率大于运行效率。使用向上转型,抽象层次高,代码的使用/理解成本更低。(能向上转型就向上转型。)

多态的本质

封装本质上是让调用者不再了解类实现的细节,从而降低了学习和使用成本。

多态则是在封装基础上更进一步,更是让你不知道当前是啥类。更不用说类的实现细节,你只需要关心它的父类。

就如这里,

只需要看到Runnable runnable(runnable是Thread类型)。而不需要关心后面new 了一个怎么样的runnable

只需要看到Thread t(t是Thread类型),而不需要关心后面new 了 一个怎样的Thread。属于将封装程度更加提高了。学习成本更降低了。

未来在公司中,接触到的各种代码,向上转型,也会非常普遍。

代码比较复杂,容易体会到封装/多态 的意义,

两次创建线程的差别

解耦合。

使用Runnable的写法,和直接继承Thread之间的区别主要是 解耦合。

相互影响越大,我们认为是耦合越高

创建线程,需要两个关键操作:

1.明确线程要执行的任务,

2.调用系统api,创建出线程。

任务本身,不一定和线程 概念 强相关,

这个任务只是单纯的执行一段代码,这个任务是使用单线程,还是多线程执行,还是通过其他方法(信号处理函数 / 协程 / 线程池)都没啥区别。

因此我们可以把任务本身给提取出来~我们将任务和线程之间进行解耦合,解耦合之后,我们随时就可以把任务改成其他方式来去执行。


匿名内部类

在数据结构课程的优先级队列中,我们学到过,能够按照优先级高低,来决定谁先出队列。

如果存的对象,那么谁优先级算高,谁算低呢,此时我们使用Compareble或者Comparator来定义,比较规则.

使用这个的时候,我们就可以使用匿名内部类来进行定义了

③.继承Thread重写run,但是使用匿名内部类.

public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(){

        };
    }
}

先创建出新的类,这个类的名字是啥,不知道

只知道这个类,是Thread的子类

同时又把这个子类的实例给创建出来了

(不知道这个类名,不影响,因为这个类本身就是只使用一次) 

package thread;

public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                while (true){
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        t.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

这样看起来更直观。通过匿名内部类创建线程,这个方法本质上和方法①是一样的。只是换了一种写法。匿名内部类这种写法,在当前java中是比较常见的

④.实现Runnable,重写run,也是使用匿名内部类。

package thread;

public class Demo41 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println("hello Thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        Thread t = new Thread(runnable);
        t.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

可以看到这样创建所执行的效果和之前也都是一样的,使用这种方式也是匿名内部类的写法。甚至我们可以连Runnable的变量名都可以不要。如下:

package thread;

public class Demo42 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println("hello Thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });

        t.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

总之匿名内部类在java中很常见,要重点掌握哦!

⑤.基于lambda表达式(最推荐写法)

package thread;

public class Demo5 {
    public static void main(String[] args) {

        Thread t = new Thread(()->{
            while (true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();

        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

lambda(枚举,反射),是一种更简化的语法表示方式,我们称之为“语法糖”

例如for each遍历数组。

for(int i = 0; i<arr.length; i++) 这是一种遍历方式

for(int x : arr)           这也是一种遍历方式,这种更简洁,可以称之为语法糖

相当于匿名内部类的替换写法

       Thread t = new Thread(()->{
            
        });

lambda表达式:

这样的写法称之为lambda表达式,其本质上是一个匿名方法。匿名函数(用一次就不用了)。主要用来实现这种“回调函数”的效果。

Java中不允许函数独立存在,必须依托于一个类,(其他语言叫函数function,java这里叫做方法method)

因此这里个代码里面,看起来像是一个单独的函数,本质上是一个函数式接口,还是一个类或者是一个对象。lambda 本质是一个函数式接口(本质上还是没脱离类)。

函数指针:

是指向内存空间的,函数怎么跑到内存中,原因是操作系统,加载 一个可执行程序,创建进程的过程。当写的代码都是一个一个的文件,我们将他们预编译,得到一个exe.文件。还是一个文件,这个时候,函数在文件里,但是当我们双击exe文件,操作系统就会 加载这个exe文件,将exe文件中的指令和数据加载到内存中,构建成一个进程,这个时候,我们写的函数,对应的二进制指令就进入到内存中,这个时候拿指针指向它。 

作用:

1.使用函数指针实现转移表,降低代码的圈复杂度(减少 if else 分支数目)

2.使用函数指针作为回调函数(qsort)

回调函数:

回调函数,不是你主动调用,也不是现在就立即调用,而是把调用的机会交给别人(通常是操作系统,库,框架,别人写的代码)来进行使用,别人会在合适的时机来调用这个函数。

java中可以使用,lambda表达式和匿名内部类来描述这个回调函数。

八、Thread类的其他使用方法

        Thread t1 = new Thread();
        Thread t2 = new Thread(new MyRunnable());
        Thread t3 = new Thread("这是我的名字");
        Thread t4 = new Thread(new MyRunnable(),"这是我的名字");

前两个我们已经见过了,第三个

8.1 Thread(String name)方法

name,在创建线程的时候,我们可以去指定一个name,name不影响线程执行,只是给线程起个名字,后续在调试的时候,比较方便区分。

使用示例如下:

package thread;

public class Demo6 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            while (true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"这是新线程");
        t.start();
    }
}

运行后,我们在JDK目录中,运行 jconsole.exe文件。

我们发现main线程咋不见了?

因为main执行完了,所以就没有了!!

线程的入口方法执行结束,则这个线程就自动销毁了

对于主线程来说,入口就是main方法

t.start();这个方法,一瞬间就执行完了。当start方法执行完毕之后,紧接着main方法执行结束,那么这个主线程自然没有了,销毁了。线程不是创建了就一直存在,而是执行完了自然就销毁了。

8.2 Tread的几个常见属性

属性                        获取方法

ID                             getId()       ID是线程唯一身份标识,不同线程不会重复。(这个id是                                                          Java给你这个线程分配的,不是系统api提供的线程id,更不                                                    是pcb中的id)

名称                         getName()    

状态                         getState()     就绪,阻塞...等等许多后面我们再去讨论。

优先级                      getPriority()     虽然提供了api可以设置/获取优先级,但是没什么大用,

                                                         从应用程序角度出发,很难察觉出来,优先级带来的差                                                          异,优先级影响到的是系统在微观上进行的调度。

                                                         后面我们再去详细讨论。

是否后台线程           isDaemon()  重点介绍:这个也叫做守护线程(后台线程) ,相对的有                                                       前台线程,如果前台线程没有执行结束,此时整个进程是                                                         一定不会结束的。而后台进程没有执行结束,并不影响整                                                         个进程的结束。默认情况下,一个线程是前台线程,除非                                                         把他手动定义成setDaemon(true) 。

是否存活                  isAlive()      Thread对象的生命周期,要比系统内核中的线程更长一些

                                                    Thread对象还在,内核中的线程已经销毁了这样的情况

                                                    我们可以通过isAlive判定内核线程是不是已经没了

                                                    回调方法执行完毕,线程就没了

是否被中断              isInterrupted()

是否后台线程           isDaemon() 

示例:

package thread;

public class Demo6 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            while (true){
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"这是新线程");

        //设置t为后台线程
        t.setDaemon(true);

        t.start();

    }
}

运行程序,我们发现什么都没有打印,

改成后台线程之后,主线程飞快执行完了,于是进程结束,t线程还没来得及执行,就完了。

是否存活                  isAlive() 

package thread;

public class Demo7 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            System.out.println("线程开始");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程结束");
        });

        t.start();
        System.out.println(t.isAlive());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(t.isAlive());

    }

}

我们让主线程sleep3000 ,所以当sleep3000完成之后,那么这个线程执行完毕。我们打印       System.out.println(t.isAlive());

 如果线程正在运行,我们调用isAlive() 那么就打印true

如果线程结束,我们调用isAlive() 那么就打印false

true和线程开始,这两条日志,谁先打印,谁后打印不一定,因为线程是并发执行的,并发调度顺序不确定,取决于系统的调度器,(自己尝试,大概率先打印true,因为调用start之后,新的线程被创建也是有一定开销的,创建线程过程中,主线程就执行println)

但是无法排除极端情况,比如主线程正好卡了下,使新线程的日志先打印......

如果在t.start前调用isLive这个时候线程没被创建出来,自然也会打印false

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

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

相关文章

Python装饰器:让函数更强大

在Python编程中&#xff0c;装饰器是一个强大的工具&#xff0c;它允许程序员以一种简洁优雅的方式修改或增强函数的行为&#xff0c;而无需更改其源代码。本文将介绍装饰器的基本概念、工作原理以及一些实际应用案例。 什么是装饰器&#xff1f; 装饰器本质上是一个接受函数…

Python酷库之旅-第三方库Pandas(049)

目录 一、用法精讲 176、pandas.Series.rank方法 176-1、语法 176-2、参数 176-3、功能 176-4、返回值 176-5、说明 176-6、用法 176-6-1、数据准备 176-6-2、代码示例 176-6-3、结果输出 177、pandas.Series.sem方法 177-1、语法 177-2、参数 177-3、功能 177…

【C语言】数组名的不同情况

前言 在C语言中&#xff0c;数组名的行为在不同的上下文中有细微的区别。数组名本质上是一个指向数组第一个元素的指针&#xff0c;但在某些情况下&#xff0c;它的行为会有所不同。以下是一些关键点&#xff0c;帮助你理解数组名在什么情况下代表第一个元素&#xff0c;什么情…

前端江湖:从菜鸟到大侠的修炼手册

在这个数字编织的梦幻世界里&#xff0c;前端&#xff0c;这个听起来就带着几分仙气与神秘感的词汇&#xff0c;实则是每一位互联网探险家手中的魔法杖。它不仅连接着代码的冰冷逻辑与用户的炽热情感&#xff0c;更在无数次的点击与滑动间&#xff0c;绘制出一幅幅绚丽多彩的交…

智慧港口整体解决方案

1. 智慧港口概况与建设意义 智慧港口建设对创新驱动和转型发展具有重要推动作用&#xff0c;是港口发展的主要方向。它通过物联网、移动互联网、云计算、人工智能等高新技术与港口功能的融合&#xff0c;提升港口的智能化和信息化水平&#xff0c;对国家可持续发展具有重要意义…

一键将桌面资料存到d盘的工具,小巧、绿色、免费、免安装

为了提升我们的系统稳定性以及资料的安全性&#xff0c;建议大家将电脑桌面的资料默认路径设置为D盘或其他磁盘&#xff0c;这样不仅会减少系统盘的占用空间&#xff0c;在系统盘出现故障时我们还可以通过pe工具备份桌面的资料。虽然我们也可以通过一些操作来修改桌面文件以及我…

视频主题Qinmei 3.0视频站源码_WordPress影视视频主题/附详细安装教程

Qinmei 3.0主题主要是将 wordpress 改造成纯 api 的站点&#xff0c;以便实现前后端分离的技术栈&#xff0c;目前的进度已经大致完成&#xff0c;唯一的问题就是需要安装 JWT token 插件。 功能介绍&#xff1a; 支持豆瓣以及 bangumi 的一键获取信息, 豆瓣 api 目前使用的是…

科普文:分布式架构中的三高:高并发、高性能、高可用

关于高并发 高并发场景 互联网应用以及云计算的普及&#xff0c;使得架构设计和软件技术的关注点从如何实现复杂的业务逻 辑&#xff0c;转变为如何满足大量用户的高并发访问请求。 一个简单的计算处理过程&#xff0c;如果一旦面对大量的用户访问&#xff0c;整个技术挑战就…

Java1.0标准之重要特性及用法实例(十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列…

aspeed 2600适配u-boot/kernel

1.说明 本文采取aspeed sdk v09.01版本来适配自己的实际的硬件开发平台。!!非采取qemu模拟方式!! 2.采用的镜像文件 2.1 采用网站编译好的镜像 采用网站: https://github.com/AspeedTech-BMC/openbmc/releases的文件: 1.ast2600-default-515-obmc.tar.gz.选择里面的文件ima…

【初阶数据结构篇】单链表的实现(赋源码)

文章目录 单链表的实现代码位置概念与结构概念&#xff1a;结构&#xff1a; 链表的性质链表的分类单链表的实现单链表的创建和打印及销毁单链表的创建单链表的打印单链表的销毁 单链表的插入单链表头插单链表尾插单链表在指定位置之前插入数据单链表在指定位置之后插入数据 单…

ChatTTS(文本转语音) 一键本地安装爆火语音模型

想不想让你喜欢的文章&#xff0c;有着一个动听的配音&#xff0c;没错&#xff0c;他就可以实现。 ChatTTS 是一款专为对话场景设计的文本转语音模型&#xff0c;例如 LLM 助手对话任务。它支持英语和中文两种语言。 当下爆火模型&#xff0c;在Git收获23.5k的Star&#xff…

Flink-CDC解析(第47天)

前言 本文主要概述了Flink-CDC. 1. CDC 概述 1.1 什么是CDC&#xff1f; CDC是&#xff08;Change Data Capture 变更数据获取&#xff09;的简称 &#xff0c;在广义的概念上&#xff0c;只要是能捕获数据变更的技术&#xff0c;都可以称之为 CDC。 核心思想是&#xff0c…

【C语言】【数据结构】二分查找(数组的练习)

目录 一、什么是二分查找 二、算法思想 2.1、概述 2.2、举例 &#xff08;1&#xff09;查找3&#xff08;数组里面存在的数&#xff09; &#xff08;2&#xff09;查找12&#xff08;数组里面不存在的数&#xff09; 三、代码实现 四、计算mid公式的优化 一、…

二阶段测试:

二阶段测试&#xff1a; 架构&#xff1a; 服务器类型部署组件ip地址DR1调度服务器 主&#xff08;ha01&#xff09;KeepalivedLVS-DR192.168.60.30DR2调度服务器 备 (ha02)KeepalivedLVS-DR192.168.60.40web1节点服务器 (slave01)NginxTomcatMySQL 备MHA managerMHA node192.…

Open3D 点云按xyz轴等距切片

目录 一、概述 1.1原理 1.2实现步骤 1.3应用 二、代码实现 2.1关键函数 2.2完整代码 三、实现效果 3.1原始点云 3.2按x轴切片 3.3按y轴切片 3.4按z轴切片 Open3D点云算法汇总及实战案例汇总的目录地址&#xff1a; Open3D点云算法与点云深度学习案例汇总&#xff…

计算机网络通信基础概念

目录 1、网络通信的本质 2、网络的发展 3、网络协议&#xff08;TCP\IP协议&#xff09; 3.1 协议实现通信的原理 3.2 协议的具体概念 3.3 协议的模型 4、数据链路层 5、网络协议栈和操作系统的关系 6、网络协议通信过程 6.1 通信过程的封装与解包 7、以太网通信…

助力樱桃智能自动化采摘,基于嵌入式端超轻量级模型LeYOLO全系列【n/s/m/l】参数模型开发构建果园种植采摘场景下樱桃成熟度智能检测识别系统

随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透到我们生活的方方面面&#xff0c;从智能家居到自动驾驶&#xff0c;再到医疗健康&#xff0c;其影响力无处不在。然而&#xff0c;当我们把目光转向中国的农业领域时&#xff0c;一个令人惊讶的…

【AI】SpringCloudAlibaba AI 学习

Spring Cloud Alibaba AI 简介 Spring Cloud Alibaba AI 以 Spring AI 为基础&#xff0c;并在此基础上提供阿里云通义系列大模型全面适配&#xff0c;让用户在 5 分钟内开发基于通义大模型的 Java AI 应用。 官网&#xff1a; https://sca.aliyun.com/ https://sca.aliyun.co…