【Java多线程】:理解线程创建、特性及后台进程

 📃个人主页:island1314

⛺️  欢迎关注:👍点赞 👂🏽留言 😍收藏  💞 💞 💞


一、背景 -- 进程与线程🚀 

🔥 多线程是提升程序性能非常重要的一种方式,也是Java编程中的一项重要技术。在程序设计中,多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此可以通信。

🥝 1. 进程

🌈 进程(process)是计算机中程序的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

注意:虽然进程在程序执行时产生,但进程并不是程序

  1. 程序是“死”的,进程是“活”的
  2. 程序是指编译好的二进制文件,它存放在磁盘上,不占用系统资源,是具体的
  3. 而进程存在于内存中,占用系统资源,是抽象的。当一次程序执行结束时,进程随之消失,进程所用的资源被系统回收

对计算机用户而言,计算机似乎能够同时执行多个进程,如听音乐、玩游戏、语音聊天等,都能在同一台计算机上同时进行。但实际上,一个单核的CPU同一时刻只能处理一个进程,用户之所以认为同时会有多个进程在运行,是因为计算机系统采用了多道程序设计技术

多道程序设计技术(了解)

  • 所谓多道程序设计,是指计算机允许多个相互独立的程序同时进入内存,在内存的管理控制之下,相互之间穿插运行。多道程序设计必须有硬件基础作为保障
  • 采用多道程序设计的系统,会将CPU的周期划分为长度相同的时间片,在每个CPU时间片内只处理一个进程,也就是说,在多个时间片内,系统会让多个进程分时使用CPU

 假如现在内存中只有3个进程——A、B、C,那么CPU时间片的分配情况大致如下

  

🐸 虽然在同一个时间片中,CPU只能处理一个进程,但CPU划分的时间片是非常微小的,且CPU运行速度极快(1秒可执行约10亿条指令)

  • 因此,在宏观上,可以认为计算机能并发执行多个程序、处理多个进程。

🦌 进程对 CPU 的使用权是由操作系统内核分配的,操作系统内核必须知道内存中有多少个进程,并且知道此时正在使用CPU的进程,这就要求内核必须能够区分进程,并可获取进程的相关属性。

🥝 2. 线程

🔥 通过上面可以知道,每个运行的程序都是一个进程,在一个进程中还可以有多个执行单元同时运行,这些执行单元可以看作程序执行的线程(thread)。每一个进程中都至少存在一个线程。

  • 例如,当一个Java程序启动时,就会产生一个进程,该进程默认创建一个线程,这个线程会运行main()方法中的代码。

单线程与多线程

  • 如果代码都是按照调用顺序依次往下执行的,没有出现两段程序代码交替运行的效果,这样的程序称作单线程程序
  • 如果希望程序中实现多段程序代码交替运行的效果,则需要创建多个线程,即多线程程序

所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程在运行时是相互独立的,它可以并发执行的。过程如下:

  

💢 注意:多条线程看起来是同时执行的;其实不然,它们和进程一样,也是由CPU轮流执行的,只不过CPU运行速度很快,因此给人同时执行的感觉。

🥝 3. 进程 VS 线程

3.1 线程的优点

  1. 创建一个新线程的代价要比创建一个新进程小得多
  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  3. 线程占用的资源要比进程少很多
  4. 能充分利用多处理器的可并行数量
  5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

3.2 进程与线程的区别

  1. 进程是系统进行资源分配和调度的一个独立单位线程程序执行的最小单位
  2. 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如:寄存器和栈
  3. 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信
  4. 线程的创建、切换及终止效率更高

二、线程的创建 🖊

💢 Java 提供了 3 种多线程的创建方式:

  1. 继承 java.lang包中的 Thrad类,重写 Thrad类的  run() 方法,在run()方法中实现多线程代码。
  2. 实现 java.lang.Runnable 接口,在 run()方法中实现多线程代码。
  3. 实现 java.util.concurrent.Callable 接口,重写 call()方法,并使用Future接口获取

♻️1. 继承 Thread 类创建多线程

在学习多线程之前,我们先来看一个代码

class MyThread{
    public void run() {
        while(true) {
            System.out.println("MyThread类的 run() 方法在运行");
        }
    }
}

public class thread_create {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.run();
        while(true) {
            System.out.println("main()方法在运行");
        }
    }
}
  • 显而易见,上面代码输出是个死循环,一直打印  MyThread类的 run() 方法在运行

⚜️如果我们想让上面代码中的 两个while 循环中的语句能够 并发执行,就需要去实现 多线程

  • 为此Java 提供了线程类 Thread。通过继承Thread类类,并重写Thread类中的 run()方法,便可实现多线程。
  • Thread类 中提供了 start()  方法用于启动新线程。
  • 新线程启动后,Java虚拟机会自动调用 run()方法;如果子类重写了run()方法,便会执行子类中的 run()方法

修改后的代码如下:

class MyThread extends Thread{
    public void run() {
        while(true) {
            System.out.println("MyThread类的 run() 方法在运行");
        }
    }
}

public class thread_create {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start(); // 开启线程
        while(true) {
            System.out.println("main()方法在运行");
        }
    }
}

 运行如下:

  • 由上图可知,两个循环中的语句都输出到控制台,说明实现了多线程。

为了使读者更好地理解单线程程序和多线程程序的执行过程,下面通过下图分析单线程和多线程的区别

    

从上图可以看出

  1. 单线程程序在运行时,会按照代码的调用顺序执行
  2. 而在多线程程序中,main() 方法和 MyThread 类的 run()  方法可以同时运行,互不影响。

♻️2. 实现 Runnable 接口创建多线程

🔥 上面通过继承Thread类实现了多线程,但是这种方式有一定的局限性。因为Java只支持单继承,一个类一旦继承了某个父类,就无法再继承 Thread类

  • 例如,Student类继承了Person类,那么Student 类就无法再通过继承Thread类创建线程

🐸 为了克服这种弊端,Thread类提供了另一个构造方法 ---- Thread(Runnable target)

  • 其中参数类型 Runnable 是一个接口,它只有一个 run()方法。
  • 当通过 Thread(Runnable target) 构造方法创建线程对象时,只需为该方法传递一个实现了Runnable 接口的对象,这样,创建的线程将实现 Runnable 接口中的 run() 方法作为运行代码,而不需要调用 Thread 类中的 run() 方法。

如下:

class MyThread implements Runnable{
    public void run() {
        while(true) {
            System.out.println("MyThread类的 run() 方法在运行");
        }
    }
}
public class thread_create {
    public static void main(String[] args) {
        MyThread myThread = new MyThread(); // 创建实例对象
        Thread thread = new Thread(myThread); // 创建线程对象
        thread.start(); // 开启线程
        while(true) {
            System.out.println("main()方法在运行");
        }
    }
}

 运行如下:

  • 此时 main() 方法 和 MyThread 类中的 run() 方法都被实现了,说明实现了多线程

♻️3. 实现 Callable 接口创建多线程

🔖 通过 Thread 类和  Runnable 接口实现多线程时,需要重写run()方法,但是由于run()方法没有返回值,无法从新线程中获取返回结果。为了解决这个问题,Java 提供了Callable接口来满足这种既能创建新线程又有返回值的需求。

通过实现 Callable接口的方式创建并启动线程的主要步骤如下:

  1. 创建 Callable接口的实现类,同时重写Callable接口的 call() 方法
  2. 创建 Callable接口的实现类对象
  3. 通过线程结果处理类 FutureTask的有参构造方法封装 Callable接口的实现类对象
  4. 调用参数为 FutureTask类对象的有参构造方法 Thread()创建Thread线程实例。
  5. 调用线程实例的 start()方法启动线程。
// 定义实现 Callable 的实现类
class MyThread implements Callable<Object> {
    // 重写 call 方法
    public Object call() throws Exception {
        int i = 0;
        while (i++ < 3){
            System.out.println(Thread.currentThread().getName() + "的方法在运行");
        }
        return i;
    }
}


public class thread_create {
    public static void main(String[] args) throws InterruptedException,ExecutionException{
        MyThread myThread = new MyThread(); // 创建实例对象
        // 使用 FutureTask 封装
        FutureTask<Object> ft = new FutureTask<>(myThread);
        // 使用 Thread(Runnable target, string name) 构造方法创建线程对象
        Thread thread = new Thread(ft, "thread");

        thread.start(); // 启动线程
        // 通过FutureTask 对象的方法管理返回值
        System.out.println(Thread.currentThread().getName() + "的返回结果:" + ft.get());

        int i = 0;
        while(i++ < 3) {
            System.out.println("main()方法在运行");
        }
    }
}

运行结果如下:

  • 上面代码 通过实现 Callable 接口的方式实现了多线程 并且还有返回结果

🌙  Callable 接口方式实现的多线程是通过 FutureTask类来封装和管理返回结果的,FutureTask类的直接父接口是 RunnableFuture,从名称上可以看出 RunnableFutureRunnableFuture的结合体。

  • FutureTask 类的继承关系如下

由上图 可以知道:FutureTask 的本质是 Runnable 接口 和 Future 接口的实现类。其中 Future 接口用于管理线程返回结果,它共有 5 个方法如下:

方法声明功能描述
boolean cancel (boolean mayInterruptIfRunning)用于取消任务。 参数 mayInterruptIfRunning 表示是否允许取消正在执行却没有执行完毕的任务。若参数设为 true,则表示可以取消正在执行的任务
boolean isCancelled ()判断任务是否被成功取消
boolean isDone ()判断任务是否已完成
V get()用于获取执行结果。注意:这个方法会发生堵塞,一直等到任务执行完毕才返回执行结果
V get (long timeout, TimeUnit unit)用于在指定时间内获取执行结果

♻️4. Thread 类 与 Runnable 接口 实现多线程对比

🌈多线程的实现方式有3种,其中Runnable 接口和 Callable 接口实现多线程的方式基本相同,主要区别就是Callable接口中的方法有返回值而 Runnable 接口中的方法没有返回值。

通过继承 Thread 类和实现 Runnable 接口实现多线程方式会有一定的区别

下面通过一个应用场景来分析说明:

  • 假设售票厅有 2 个窗口可发售某日某次列车的 3 张车票。
  • 这时,3 张车票可以看作共享资源;2 个售票窗口同时售票,可以看作  2 个线程同时运行。
  • 为了更直观地显示窗口的售票情况,可以调用  Thread 类的 currentThread()  方法获取当前线程的实例对象,然后调用 getName() 方以获取线程的名称。

(1)首先通过继承 Thread 类的方式创建多线程来演示售票情景,如下:

class TicketWindow extends Thread{
    private int tickets = 3;
    public void run(){
        while (tickets > 0){
            Thread t = Thread.currentThread(); // 获取当前线程
            String t_name = t.getName(); // 获取当前线程名字
            System.out.println(t_name +  "正在发售第 " + tickets-- + " 张票");
        }
    }
}

public class thread_ticket {
    public static void main(String[] args) {
        new TicketWindow().start();
        new TicketWindow().start();
    }
}

运行结果如下:

从上图可以看出,每张票都被打印了 2 次。出现这个现象的原因是  2 个线程没有共享 3 张票,而是各自出售了 3 张票

  • 在程序中创建了2 个 TicketWindow 对象,就等于创建了 2 个售票线程,每个线程中都有 3 张票,每个线程在独立地处理各自的资源。

需要注意的是:上面的每个线程都有自己的名字,主线程默认的名字是main,用户创建的第一个线程的名字默认为Thread-0,第二个线程的名字默认为Thread-1,以此类推。
由于现实中铁路系统的车票资源是共享的,因此上面的运行结果显然不合理

为了保证资源共享在程序中只能创建一个售票对象,然后开启多个线程运行同一个售票对象的售票方法,简单来说,就是 2 个线程运行同一个售票程序

  1. 用Thread类创建多线程,无法保证多个线程对共享资源的正确操作
  2. 而Runnable接口可以保证多个线程对共享资源的正确访问。接下来,通过实现Runnable 接口的方式实现多线程的创建。

修改代码使用构造方法 Thread(Runnable target, String name) 在创建线程对象时指定线程名称

class TicketWindow implements Runnable{
    private int tickets = 3;
    public void run(){
        while (tickets > 0){
            Thread t = Thread.currentThread(); // 获取当前线程
            String t_name = t.getName(); // 获取当前线程名字
            System.out.println(t_name +  "正在发售第 " + tickets-- + " 张票");
        }
    }
}

public class thread_ticket {
    public static void main(String[] args) {
        TicketWindow tw = new TicketWindow();
        new Thread(tw, "窗口1").start();
        new Thread(tw, "窗口2").start();
    }
}

运行结果如下:

注意:Thread 类下 也可以实现资源共享,还记得我们之前学的 static 全局共享嘛,我们可以给 ticket 票加上这个属性,就可以使所有窗口共享资源了,如下:

三、后台线程

🔥 在多线程中,主线程如果不等待子线程的返回结果,那么主线程与子线程没有先后顺序,有可能主线程先结束了,子线程还没结束。

  • 在这样的情况下,虽然主线程结束了,但整个程序进程是不会结束的,因为子线程还在执行。

💧 有人可能会认为,当 main() 方法中创建并启动的  2 个新线程的代码执行完毕后,主线程也就随之结束了。然而,通过程序的运行结果可以看出,虽然主线程结束了,但整个Java程序却没有随之结束,仍然在执行售票的代码。

对Java程序来说,只要还有一个前台线程在运行,这个进程就不会结束;如果一个进程中只有后台线程运行,这个进程就会结束。

🎐注意:这里提到的前台线程和后台线程是一种相对的概念

  1. 新创建的线程默认是前台线程
  2. 如果某个线程对象在启动之前执行了 setDaemon(true)语句,这个线程就变成了后台线程。
class DemoThread implements Runnable{
    public void run() {
        while(true){
            System.out.println(Thread.currentThread().getName() + "---在运行");
        }
    }
}

public class thread_back {
    public static void main(String[] args) {
        // 判断是否为后台线程
        System.out.println("main 线程是后台线程嘛? --> " + Thread.currentThread().isDaemon());

        DemoThread dt = new DemoThread();
        Thread thread = new Thread(dt, "后台线程");
        System.out.println("thread 线程默认是后台线程嘛? --> " + thread.isDaemon());

        // 将线程 thread 对象设置为 后台线程
        thread.setDaemon(true);
        thread.start();
        for(int i = 1;i <= 3; i++) {
            System.out.println(i);
        }
    }
}

上面演示了一个后台线程结束的过程,运行如下

此时结果竟然没有出现死循环的情况,分析如下:

  • 如果将线程thread设置为后台线程,当前台主线程循环输出任务执行完毕后,整个进程就会结束,此时Java虚拟机也会通知后台线程结束
  • 由于后台线程从接收指令到作出响应需要一定的时间,因此,输出了几次 “后台线程---在运行” 语句后,后台线程也结束了
  • 由此说明:在进程中不存在前台线程时,整个进程就会结束

注意:要使某个线程设置为后台线程,必须在该线程启动之前进行设置,也就是说 setDaemon() 方法必须在 start() 方法之前进行调用,否则后台进程设置无效

四、 线程创建变形(了解)📚

💫 1. 匿名内部类创建 Thread 子类对象

public static void main(String[] args) {
    // 匿名内部类 继承 Thread,重写run
    Thread t =new Thread() {
        @Override
        public void run() {
            System.out.println("使用匿名类创建 Thread 子类对象");
        }
    };

    t.start();
    System.out.println("main () 主方法");
}

💫 2. 匿名内部类创建 Runnable 子类对象

public static void main(String[] args) {
    // 匿名内部类 实现 Runnable,重写run
    Thread t = new Thread(new Runnable(){
        @Override
        public void run() {
            System.out.println("使用匿名类创建 Runnable 子类对象");
        }
    });

    t.start();
    System.out.println("main () 主方法");
}

💫 3. lambda 表达式

lambda 表达式可以简化多线程的创建和调用过程,在创建线程时候可以指定线程调用的方法,格式如下:

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

使用如下:

public static void main(String[] args) {
    Thread t = new Thread(() ->{
        System.out.println("lambda 表达式创建 Runnable 子类对象");
    });

    t.start();
    System.out.println("main () 主方法");
}

5. 多线程的优势 -- 增加运行速度💦

可以观察多线程在一些场合下是可以提高程序的整体运行效率的。

  1. 使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
  2. serial 串行的完成一系列运算
  3. concurrency 使两个线程并行的完成同样的运算.
public class thread_advantage {
    private static void concurrency() throws InterruptedException {
        long begin = System.nanoTime();

        // 利用一个线程计算 a 的值
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                int a = 0;
                for (long i = 0; i < count; i++) {
                    a--;
                }
            }
        });
        thread.start();
        // 主线程内计算 b 的值
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        // 等待 thread 线程运行结束
        thread.join();

        // 统计耗时
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("并发: %f 毫秒%n", ms);
    }

    private static void serial() {
        // 全部在主线程内计算 a、b 的值
        long begin = System.nanoTime();
        int a = 0;
        for (long i = 0; i < count; i++) {
            a--;
        }
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("串行: %f 毫秒%n", ms);
    }
    
    // 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的
    private static final long count = 10_0000_0000;
    public static void main(String[] args) throws InterruptedException {
        // 使用并发方式
        concurrency();
        // 使用串行方式
        serial();
    }
}

// 运行结果如下:
并发: 381.155400 毫秒
串行: 674.797500 毫秒

6. 小结📖

以上我们就把线程的创建、后台线程、以及进程与线程的区别讲啦,希望大家可以通过这篇文章打开对多线程学习的大门,后面我会继续更新多线程的相关知识的呀

【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束啦,如果我的这篇博客可以给你提供有益的参考和启示,可以三连支持一下 !!

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

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

相关文章

Matlab实现海马优化算法(SHO)求解路径规划问题

目录 1.内容介绍 2.部分代码 3.实验结果 4.内容获取 1内容介绍 海马优化算法&#xff08;SHO&#xff09;是一种受自然界海马行为启发的优化算法&#xff0c;它通过模拟海马在寻找食物和配偶时的探索、跟踪和聚集行为来搜索最优解。SHO因其高效的全局搜索能力和局部搜索能力而…

【初阶数据结构与算法】复杂度分析练习之轮转数组(多种方法)

文章目录 复杂度练习之轮转数组方法1方法2方法3 总结 复杂度练习之轮转数组 题目链接&#xff1a;https://leetcode.cn/problems/rotate-array/description/    为什么我们把这道题作为复杂度的练习题呢&#xff1f;是因为我们以后做题都会涉及到复杂度的计算&#xff0c;我…

哲学家就餐问题(Java实现信号量和PV操作)

哲学家就餐是经典的PV操作。 一个哲学家同时拿起左边的筷子和右边的筷子进行就餐&#xff0c;每一个哲学家都会等待右边的筷子&#xff0c;具备了死锁问题之一的循环等待。 基础的哲学家就餐问题代码 在Java中&#xff0c;Semaphore 是一个用于控制对某个资源的访问的同步工具…

mutable用法

mutable 关键字用于允许类的某个成员变量在 const 成员函数中被修改。通常&#xff0c;const 成员函数不能改变对象的任何成员变量&#xff0c;但将成员变量声明为 mutable 可以例外 class Hero { public:Hero():m_Hp(0), m_getHpCounter(0){}int getHp() const {m_getHpCounte…

C++ | Leetcode C++题解之第537题复数乘法

题目&#xff1a; 题解&#xff1a; class Solution { public:string complexNumberMultiply(string num1, string num2) {regex re("\\|i"); vector<string> complex1(sregex_token_iterator(num1.begin(), num1.end(), re, -1), std::sregex_token_iterator…

告别传统营销,HubSpot AI分析工具带你玩转新潮流

你们知道吗&#xff1f;现在的人工智能&#xff08;AI&#xff09;技术可是越来越厉害了&#xff0c;它简直就是我们营销人员的超级外挂&#xff01;有了AI分析工具&#xff0c;我们不仅能优化营销效果&#xff0c;还能大大提升工作效率。那么&#xff0c;具体是怎么一回事呢&a…

Docker打包自己项目推到Docker hub仓库(windows10)

一、启用Hyper-V和容器特性 1.应用和功能 2.点击程序和功能 3.启用或关闭Windows功能 4.开启Hyper-V 和 容器特性 记得重启生效&#xff01;&#xff01;&#xff01; 二、安装WSL2&#xff1a;写文章-CSDN创作中心https://mp.csdn.net/mp_blog/creation/editor/143057041 三…

如何删除react项目的默认图标,使在浏览器中不显示默认图标favicon.ico

要删除 React 项目的默认图标&#xff0c;使在浏览器中不显示默认图标favicon.ico&#xff0c;其实有两种方法&#xff1a; 方法一 方法要点&#xff1a;删除掉 public 目录下的 favicon.ico 文件&#xff0c;再用浏览器访问时&#xff0c;如果加载不到图标文件&#xff0c;就…

软件测试:测试用例详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、通用测试用例八要素   1、用例编号&#xff1b;    2、测试项目&#xff1b;   3、测试标题&#xff1b; 4、重要级别&#xff1b;    5、预置…

支付幂等性的实现中,通过“一锁、二判、三更新”

在这个支付幂等性的实现中&#xff0c;通过“一锁、二判、三更新”严格控制了支付链接生成接口的幂等性&#xff0c;确保同一业务单号在同一时间只会生成一个有效的支付链接&#xff0c;避免重复支付或其他意外操作。 Facade DistributeLock(keyExpression "#payCreate…

Charles抓包_Android

1.下载地址 2.破解方法 3.安卓调试办法 查看官方文档&#xff0c;Android N之后抓包要声明App可用User目录下的CA证书 3.1.在Proxy下进行以下设置&#xff08;路径Proxy->Proxy Settings&#xff09; 3.1.1.不抓包Windows&#xff0c;即不勾选此项&#xff0c;免得打输出不…

Chromium Mojo(IPC)进程通信演示 c++(3)

122版本自带的mojom通信例子channel-associated-interface 仅供学习参考&#xff1a; codelabs\mojo_examples\03-channel-associated-interface-freezing 其余定义参考上一篇文章&#xff1a; Chromium Mojo(IPC)进程通信演示 c&#xff08;2&#xff09;-CSDN博客​​​​…

鸢尾博客项目开源

1.博客介绍 鸢尾博客是一个基于Spring BootVue3 TypeScript ViteJavaFx的客户端和服务器端的博客系统。项目采用前端与后端分离&#xff0c;支持移动端自适应&#xff0c;配有完备的前台和后台管理功能。后端使用Sa-Token进行权限管理,支持动态菜单权限&#xff0c;服务健康…

【模型学习之路】手写+分析bert

手写分析bert 目录 前言 架构 embeddings Bertmodel 预训练任务 MLM NSP Bert 后话 netron可视化 code2flow可视化 fine tuning 前言 Attention is all you need! 读本文前&#xff0c;建议至少看懂【模型学习之路】手写分析Transformer-CSDN博客。 毕竟Bert是tr…

word及Excel常见功能使用

最近一直在整理需规文档及表格&#xff0c;Word及Excel需要熟练使用。 Word文档 清除复制过来的样式 当复制文字时&#xff0c;一般会带着字体样式&#xff0c;此时可选中该文字 并使用 ctrlshiftN 快捷键进行清除。 批注 插入->批注&#xff0c;选中文本 点击“批注”…

【C++篇】数据之林:解读二叉搜索树的优雅结构与运算哲学

文章目录 二叉搜索树详解&#xff1a;基础与基本操作前言第一章&#xff1a;二叉搜索树的概念1.1 二叉搜索树的定义1.1.1 为什么使用二叉搜索树&#xff1f; 第二章&#xff1a;二叉搜索树的性能分析2.1 最佳与最差情况2.1.1 最佳情况2.1.2 最差情况 2.2 平衡树的优势 第三章&a…

【Mac】安装 VMware Fusion Pro

VMware Fusion Pro 软件已经正式免费提供给个人用户使用&#xff01; 1、下载 【官网】 下拉找到 VMware Fusion Pro Download 登陆账号 如果没有账号&#xff0c;点击右上角 LOGIN &#xff0c;选择 REGISTER 注册信息除了邮箱外可随意填写 登陆时&#xff0c;Username为…

文心一言 VS 讯飞星火 VS chatgpt (383)-- 算法导论24.5 3题

三、对引理 24.10 的证明进行改善&#xff0c;使其可以处理最短路径权重为 ∞ ∞ ∞ 和 − ∞ -∞ −∞ 的情况。引理 24.10(三角不等式)的内容是&#xff1a;设 G ( V , E ) G(V,E) G(V,E) 为一个带权重的有向图&#xff0c;其权重函数由 w : E → R w:E→R w:E→R 给出&…

Linux 服务器使用指南:从入门到登录

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; &#x1f6a9;博主致力于用通俗易懂且不失专业性的文字&#xff0c;讲解计算机领域那些看似枯燥的知识点&#x1f6a9; 目录 一…

【Maven】——基础入门,插件安装、配置和简单使用,Maven如何设置国内源

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 引入&#xff1a; 一&#xff1a;Maven插件的安装 1&#xff1a;环境准备 2&#xff1a;创建项目 二…