首页 > 其他分享 >多线程学习

多线程学习

时间:2024-09-29 15:22:04浏览次数:11  
标签:执行 创建 学习 任务 线程 进程 多线程 等待

一. 认识线程(Thread)

1. 线程是什么

定义:线程是一个轻量级的执行流,它代表了程序执行的一个路径。每个线程都有自己的程序计数器、栈和局部变量,但线程之间可以共享同一个进程的全局变量和堆。

主线程:在 Java 程序中,main() 方法所运行的线程被称为主线程(Main Thread)。当你启动一个 Java 应用程序时,JVM 会创建一个主线程来执行 main() 方法。

执行流:每个线程可以看作是一条独立的执行路径,多个线程可以并行执行同一段代码,但它们的执行顺序是非确定性的,这意味着可能会发生交错执行(interleaving)。

2. 为啥要有线程

并发编程的需求:现代计算需求往往需要同时执行多个任务,尤其在多核处理器环境下。并发编程能够充分利用 CPU 的资源,提升程序性能和响应速度。

提高算力:多核 CPU 的发展使得并发编程成为必要。通过创建多个线程,可以将计算任务分配到多个 CPU 核心上,提高执行效率。

等待 I/O 的场景:许多程序在执行过程中会等待输入/输出(I/O)操作,如文件读取、网络请求等。使用多线程可以在等待 I/O 时,让其他线程继续执行,从而提高资源的利用率。

线程相对进程的优势

  • 创建速度:线程的创建和销毁比进程更快,因为线程的上下文切换比进程的上下文切换开销小。
  • 调度效率:操作系统调度线程的效率高于进程,线程在同一进程内的切换非常迅速。
  • 资源共享:同一进程内的线程共享内存空间,这使得数据传递更加高效。

线程池与协程

  • 线程池:为了进一步提高性能,Java 提供了线程池机制,允许预先创建一组线程,重复使用,减少频繁创建和销毁线程的开销。
  • 协程:协程是一种更轻量级的线程实现,它允许在单线程内实现并发。协程通过协作式调度管理执行状态,使得资源利用更加高效。

3. 进程和线程的区别

3.1. 基本概念
  • 进程(Process)
    • 进程是操作系统分配资源的基本单位,它是一个执行中的程序。每个进程拥有自己的地址空间、数据段、堆栈和其他属性。
  • 线程(Thread)
    • 线程是进程内部的一个执行单元,它是操作系统能够独立调度的最小单位。每个进程至少有一个主线程,多个线程可以并行执行。
3.2. 资源分配
  • 进程
    • 每个进程都有独立的内存空间,包括代码段、数据段和堆栈。进程间的内存是相互隔离的。
  • 线程
    • 同一进程中的所有线程共享进程的内存空间,包括全局变量和堆。这使得线程之间可以快速通信,但也带来了线程安全问题。
3.3. 数据共享与通信
  • 进程
    • 进程间不能直接共享数据,数据共享需要通过进程间通信(IPC)机制,如管道、消息队列、共享内存、信号量等。这使得进程间的数据交换相对复杂。
  • 线程
    • 线程可以直接访问同一进程的内存,数据共享非常简单,可以通过直接读写共享变量实现。这种共享能力虽然方便,但也可能导致数据竞争和一致性问题。
3.4. 创建与销毁
  • 进程
    • 创建和销毁进程的开销较大。创建进程时,操作系统需要分配新的内存空间和系统资源,这会消耗更多的时间和资源。
  • 线程
    • 线程的创建和销毁相对更快,因为线程共享进程的资源和内存。创建新线程只需分配少量的系统资源(如线程栈和寄存器)。
3.5. 调度与执行
  • 进程
    • 进程是操作系统调度的基本单位。进程的上下文切换涉及到更复杂的操作,如保存和恢复进程的完整状态,涉及大量的资源管理。
  • 线程
    • 线程是最小的执行单位,操作系统调度线程的开销较小。线程的上下文切换速度比进程快,因为线程共享相同的地址空间,保存和恢复的状态较少。
3.6. 崩溃影响
  • 进程
    • 如果一个进程崩溃,通常不会影响到其他进程的运行。操作系统能够保证进程之间的隔离性。
  • 线程
    • 如果一个线程崩溃,可能会导致整个进程崩溃。这是因为线程之间共享内存,崩溃的线程可能会破坏共享数据,从而影响其他线程的执行。
3.7. 应用场景
  • 进程
    • 适用于需要高隔离性的应用,如不同的服务或应用程序之间的运行(如浏览器、文本编辑器、播放器等),各个进程独立且不干扰。
  • 线程
    • 适用于需要高并发的场景,如服务器处理多个客户端请求时,使用线程可以提高响应速度和资源利用率。
3.8. 小结
特征进程线程
定义资源分配的基本单位最小的执行单元
内存空间独立的地址空间共享同一进程的内存空间
数据共享通过 IPC 机制直接访问共享内存
创建与销毁开销较大开销较小
调度系统调度的基本单位轻量级的调度单位
崩溃影响不影响其他进程可能导致整个进程崩溃
应用场景高隔离性需求(如独立服务)高并发性需求(如 web 服务器处理请求)

这种区别能帮助你全面理解进程和线程之间的不同。如果还有其他方面需要进一步探讨或具体的例子,请告诉我!

4. Java的线程和操作系统线程的关系

操作系统的支持

  • 线程是操作系统的基本概念,操作系统内核通过提供线程的创建、调度、同步等功能来支持多线程编程。
  • 不同的操作系统使用不同的机制来管理线程,例如,Linux 使用 pthread 库来提供线程功能。

Java 的线程模型

  • Java 的 Thread 类是对操作系统线程 API 的抽象,JVM 在底层将 Java 线程映射为操作系统线程。
  • Java 线程通常是用户级线程,JVM 负责调度这些线程,并将它们映射到操作系统的线程上。

线程调度

  • Java 线程的调度依赖于操作系统的调度策略,通常是时间片轮转(Round Robin)或优先级调度(Priority Scheduling)。
  • 在 Java 中,开发者可以通过 Thread 类设置线程的优先级,但实际效果还依赖于操作系统的实现。

二 Java 中创建线程的方式总结

1.继承 Thread 类

  • 描述:创建一个类继承 Thread,重写 run() 方法,使用 start() 方法启动线程。

  • 优点

    • 代码简单,易于理解。
    • 可以直接调用 Thread 类中的方法。
  • 缺点

    • Java 是单继承的,不能继承其他类。
  • 示例

    class MyThread extends Thread {
        public void run() {
            // 线程执行的代码
        }
    }
    

2.实现 Runnable 接口

  • 描述:创建一个类实现 Runnable 接口,重写 run() 方法,并将 Runnable 实例传递给 Thread 对象。

  • 优点

    • 支持多重继承,可以同时继承其他类。
    • 适合多个线程共享同一任务。
  • 缺点

    • 代码相对复杂。
  • 示例

    class MyRunnable implements Runnable {
        public void run() {
            // 线程执行的代码
        }
    }
    

3.使用 Callable 接口

  • 描述:创建一个类实现 Callable 接口,重写 call() 方法,使用 FutureTaskExecutorService 执行任务。

  • 优点

    • 可以返回结果并处理异常。
    • 更适合需要计算结果的场景。
  • 缺点

    • 需要使用 FutureTask,相对较复杂。
  • 示例

    class MyCallable implements Callable<String> {
        public String call() throws Exception {
            // 线程执行的代码
            return "结果";
        }
    }
    

4.使用 Lambda 表达式(Java 8 及以上)

  • 描述:使用 Lambda 表达式简化 RunnableCallable 的实现。

  • 优点

    • 代码简洁,减少了样板代码。
    • 更容易阅读和维护。
  • 示例

    Thread thread = new Thread(() -> {
        // 线程执行的代码
    });
    

5.小结

创建方式描述优点缺点
继承 Thread直接创建线程类并重写 run() 方法简单易懂,直接调用 Thread 方法单继承限制,无法继承其他类
实现 Runnable 接口实现接口并重写 run() 方法支持多重继承,适合共享任务代码较复杂
实现 Callable 接口实现接口并重写 call() 方法可以返回结果,处理异常相对复杂,需要使用 FutureTask
使用 Lambda 表达式使用 Lambda 简化代码代码简洁,易读仅适用于 Java 8 及以上

以上总结涵盖了 Java 中创建线程的主要方法及其优缺点,可以根据具体的应用场景选择最适合的方式。如果还有其他问题或需要进一步讨论的内容,请随时告诉我!

三. 线程的生命周期

线程在 Java 中的生命周期主要包括以下七种状态:

1.新建(New)

  • 描述:当线程对象被创建时,它处于新建状态。此时,线程尚未开始执行,且没有占用系统资源。

  • 示例:

    Thread thread = new Thread(() -> {
        // 线程执行的代码
    });
    // 此时线程处于新建状态
    

2.可运行(Runnable)

  • 描述:当调用 start() 方法后,线程进入可运行状态。可运行状态的线程可能是就绪(READY)或正在运行(RUNNING)。可运行状态的线程处于系统的可运行线程池中,等待 CPU 的调度。

    • 就绪(READY):线程已准备好,等待 CPU 时间片。
    • 运行中(RUNNING):线程获得 CPU 时间片,正在执行代码。
  • 示例:

    thread.start();  // 调用 start() 方法,线程进入可运行状态
    

3.运行(Running)

  • 描述:当线程获得 CPU 资源后,进入运行状态,开始执行 run() 方法中的代码。只有一个线程可以在任意时刻处于运行状态。

  • 示例:

    // 在这里,线程正在执行具体的任务
    

4.阻塞(Blocked):

  • 描述:线程在等待某个资源(如获取锁)而被挂起时进入阻塞状态。阻塞通常发生在多线程环境中,当一个线程试图访问一个已经被其他线程占用的资源时,它会被阻塞,无法继续执行。

  • 示例:

    synchronized (someObject) {
        // 这里是访问被锁定的资源
    }
    // 如果另一个线程已经获取了 someObject 的锁,当前线程将被阻塞
    

5.等待(Waiting)

  • 描述:线程进入等待状态时,它需要等待其他线程的特定动作(如通知或中断)。在等待状态下,线程不会占用 CPU 资源。

  • 示例:

    synchronized (someObject) {
        someObject.wait();  // 线程在这里等待
    }
    

6.超时等待(Timed Waiting)

  • 描述:线程在此状态时,会在指定的时间后自动返回。这种状态通常用于等待一定时间。

  • 示例:

    synchronized (someObject) {
        someObject.wait(1000);  // 等待最多 1000 毫秒
    }
    

7.终止(Terminated)

  • 描述:线程的执行完成或因异常中断后,进入终止状态。此时,线程生命周期结束,无法再被启动。

  • 示例:

    // run() 方法执行完毕,线程进入终止状态
    

8.线程状态的转换

  • 新建到可运行:当调用 start() 方法时,线程从新建状态转变为可运行状态。
  • 可运行到运行:当线程调度器分配 CPU 资源时,线程从可运行状态转变为运行状态。
  • 运行到阻塞:当线程尝试获取锁而未成功时,或者执行了 sleep()wait() 等方法时,线程将被阻塞。
  • 阻塞到可运行:当所等待的资源可用时,阻塞的线程会返回到可运行状态,等待再次获得 CPU 资源。
  • 运行到等待:线程在调用 wait()join() 等方法时,进入等待状态。
  • 等待到可运行:当其他线程调用了相应的通知方法(如 notify()notifyAll())时,等待的线程会返回到可运行状态。
  • 超时等待到可运行:当指定的等待时间到达时,线程会自动返回到可运行状态。
  • 运行到终止:线程的 run() 方法执行完毕或抛出未捕获异常时,线程进入终止状态。

流转图如下:

在这里插入图片描述

9.线程状态的查询

可以使用 Thread.getState() 方法来查看线程的当前状态。

四 多线程带来的风险 - 线程安全

1. 线程安全的概念

  • 定义:在多线程环境中,确保多个线程对共享资源的访问是安全的,使得程序的行为是可预测的,且数据始终保持一致。
  • 重要性:
    • 防止数据不一致。
    • 保护共享资源,确保程序逻辑的正确执行。

2. 线程安全问题的来源

  • 数据竞争(Race Condition)
    • 多个线程同时访问和修改共享变量。
    • 示例:两个线程同时递增同一个计数器,导致最终结果不正确。
  • 脏读(Dirty Read)
    • 一个线程读取到尚未提交的值。
    • 结果:可能基于错误的数据进行操作,导致程序状态异常。
  • 死锁(Deadlock)
    • 两个或多个线程互相等待对方释放锁,导致程序无法继续执行。
    • 示例:线程A持有资源1的锁并等待资源2的锁,而线程B则相反,形成循环等待。
  • 活锁(Livelock)
    • 线程不断变更状态以避免死锁,但由于状态调整没有实际工作,导致系统无法进展。

3. 线程同步的必要性

为了避免上述问题,线程间必须进行有效的同步,确保同一时刻只有一个线程可以操作共享资源。

a. 使用 synchronized
  • 同步方法:

    • 在方法声明中使用 synchronized 关键字,确保同一时间只有一个线程能执行该方法。
    • 适合简单场景,易于使用。
  • 同步代码块:

    • 只锁住特定的代码段,适用于复杂的逻辑或对性能有较高要求的场景。

    • 语法示例:

      public void increment() {
          synchronized(this) {
              count++;
          }
      }
      
b. 使用 Lock 接口
  • 特点
    • 提供更灵活的锁机制,如可重入锁、尝试锁等。
    • 适合高并发场景,能够减少锁竞争。
  • 常用实现ReentrantLock
    • 可重入性:同一线程可以多次获得同一个锁。
    • 尝试获取锁:tryLock() 方法允许线程在获取锁时不被阻塞。
c. 使用 ReadWriteLock
  • 定义:提供读锁和写锁,读锁允许多个线程同时读取,写锁独占。
  • 使用场景:适合读操作占多数的情况,例如缓存读取,能有效提高并发性能。
d. 使用 Semaphore
  • 定义:信号量用于控制同时访问特定资源的线程数量。
  • 应用:适合限流场景,比如限制数据库连接的最大数量。
e. 使用 CountDownLatch
  • 定义CountDownLatch 是一个同步辅助类,用于让一个或多个线程等待直到一组操作完成。
  • 基本原理
    • CountDownLatch 维护一个计数器,线程可以通过调用 countDown() 方法来减少计数,其他线程可以通过 await() 方法等待计数器归零。
  • 使用场景
    • 适合于等待多个线程完成初始化或其他操作的场景。例如,在进行多线程并发处理时,主线程可以等待所有工作线程完成后再继续执行。
f. 使用 join 方法
  • 定义join 方法用于让调用该方法的线程等待其他线程完成执行。
  • 基本用法:
    • 当一个线程调用另一个线程的 join() 方法时,调用线程会阻塞,直到被调用线程执行完成。
  • 使用场景:
    • 适合在需要确保某个线程完成任务后再执行后续逻辑的场合,例如在多个线程并行处理数据时,主线程需要等待所有子线程完成后再进行汇总或输出结果。

4. 线程通信的必要性

线程间有时需要交换信息或协调工作,这就是线程通信的必要性。

a. 使用 wait(), notify(), notifyAll()
  • wait()
    • 使当前线程进入等待状态,直到被其他线程通知。
    • 示例:消费者在没有可用数据时进入等待。
  • notify()
    • 唤醒一个正在等待的线程。
  • notifyAll()
    • 唤醒所有等待的线程,适用于需要通知多个线程的场景。
b. 使用 Condition
  • 定义:提供更灵活的等待/通知机制,通常与 Lock 接口结合使用。
  • 优势:支持多个等待队列,增强了线程协调能力。

5. 线程安全的设计模式

a. 生产者-消费者模式
  • 原理:生产者生成数据,消费者消费数据,通过阻塞队列实现安全通信。
  • 实现:使用 BlockingQueue,生产者在队列满时等待,消费者在队列空时等待。
b. 单例模式(线程安全实现)
  • 实现方法:双重检查锁定(Double-Checked Locking),确保单例实例在多线程环境下只被创建一次。

  • 代码示例:

    public class Singleton {
        private static volatile Singleton instance;
    
        private Singleton() {}
    
        public static Singleton getInstance() {
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

五. 线程池

1. 什么是线程池?

线程池是一种并发编程的设计模式,用于管理和复用多个线程。线程池会预先创建一定数量的线程,避免在高并发环境下频繁创建和销毁线程,从而减少资源消耗和提升性能。

2. 线程池的优势

  • 性能提升:线程的创建和销毁是昂贵的操作,线程池通过复用线程显著提高系统性能。
  • 资源控制:通过限制线程的数量,线程池可以有效防止资源耗尽。
  • 响应速度:线程池可以迅速响应新的任务请求,降低了任务的等待时间。
  • 灵活管理:线程池通过任务队列和多种任务处理策略,增强了任务的调度能力。

3. 线程池的核心组件

1. 核心线程数(corePoolSize)
  • 定义:线程池中始终保持的线程数量。
  • 作用:在任务提交时,如果当前线程数少于核心线程数,则会创建新线程执行任务。
2. 最大线程数(maximumPoolSize)
  • 定义:线程池中允许的最大线程数量。
  • 作用:限制线程池能创建的最大线程数量,防止系统资源过度消耗。
3. 空闲线程存活时间(keepAliveTime)
  • 定义:当线程数超过核心线程数时,多余的空闲线程在此时间内会被终止。
  • 作用:有助于释放资源,避免过多的空闲线程占用系统内存。
4. 时间单位(unit)
  • 定义:用于指定 keepAliveTime 的时间单位。
  • 常用单位SECONDS, MILLISECONDS, MINUTES, HOURS, DAYS
5. 任务队列(workQueue)
  • 定义:用于存放等待执行的任务的队列。
  • 常见队列类型:
    • ArrayBlockingQueue:有界队列,限制最大任务数,适合任务数量可预测的场景。
    • LinkedBlockingQueue:无界队列,适合任务数量不确定的情况,线程安全。
    • SynchronousQueue:不存储元素,每个插入操作都必须等待对应的删除操作,适合瞬时任务。
    • PriorityBlockingQueue:按照优先级处理任务,适合任务优先级差异较大的场景。
6. 拒绝策略(handler)
  • 定义:处理无法执行的任务的策略。
  • 常用策略:
    • AbortPolicy:抛出异常,任务无法执行。
    • CallerRunsPolicy:由调用线程处理任务,适合简单的任务重试。
    • DiscardPolicy:直接丢弃任务,不抛出异常。
    • DiscardOldestPolicy:丢弃队列中最旧的任务,优先处理新任务。
* 运行流程
  • 首先按核心线程数创建线程任务
  • 核心线程数满了。任务进入工作队列
  • 队列也满了 扩大核心线程数
  • 当核心线程数达到最大线程数
  • 并且工作队列也满了 触发拒绝策略
  • 工作队列分有界和无界的

4. 创建线程池的方式

在Java中,java.util.concurrent包提供了多种方式来创建线程池,其中使用Executors工厂类是最常见的方法。

1. 使用 Executors 创建线程池
  • 固定线程池:适用于处理固定数量的并发任务。

    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5); // 固定5个线程
    
  • 可缓存线程池:适合处理大量短期任务,线程会根据需要动态创建和回收。

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); // 根据需求动态创建线程
    
  • 单线程池:适用于需要保证任务按顺序执行的场景。

    ExecutorService singleThreadPool = Executors.newSingleThreadExecutor(); // 仅有一个线程
    
  • 定时任务线程池:用于周期性或延迟执行的任务。

    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); // 固定线程数的定时任务池
    
2. 使用 ThreadPoolExecutor 自定义线程池

除了使用Executors,我们还可以通过ThreadPoolExecutor类来创建更灵活的线程池:

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, // 核心线程数
    5, // 最大线程数
    60, // 空闲线程存活时间(秒)
    TimeUnit.SECONDS, // 时间单位
    new ArrayBlockingQueue<>(10), // 任务队列
    new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

5. 示例代码

以下是使用 ThreadPoolExecutor 创建一个线程池的示例代码:

import java.util.concurrent.*;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3); // 创建固定大小的线程池

        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("正在执行任务:" + taskId + ",线程:"+Thread.currentThread().getName());
                try {
                    Thread.sleep(200); // 模拟任务执行时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        executor.shutdown(); // 关闭线程池
    }
}

6. 适用场景

  • 固定大小线程池:适用于任务量稳定且可预测的场景,例如文件处理、图片上传等。
  • 可缓存线程池:适合处理大量短期任务,可以动态创建和回收线程,适合高并发场景。
  • 单线程池:适用于需要保证任务按顺序执行的场景,例如日志处理、任务调度等。
  • 定时任务线程池:适合定期执行的任务调度,例如定时备份、定时清理等。

7. 常见问题与注意事项

  • 合理配置参数:核心线程数、最大线程数和任务队列的选择应根据应用的实际需求进行合理配置,以避免资源耗尽或过度消耗。
  • 线程池的关闭:在使用完线程池后,确保调用 shutdown()shutdownNow() 以释放资源,防止内存泄漏。
  • 监控线程池状态:定期检查线程池的状态(如活跃线程数、已完成任务数等)以确保系统运行正常,避免任务堆积。

标签:执行,创建,学习,任务,线程,进程,多线程,等待
From: https://blog.csdn.net/m0_56353506/article/details/142603861

相关文章

  • 吴恩达深度学习笔记:卷积神经网络(Foundations of Convolutional Neural Networks)2.5-2.
    目录第四门课卷积神经网络(ConvolutionalNeuralNetworks)第二周深度卷积网络:实例探究(Deepconvolutionalmodels:casestudies)2.5网络中的网络以及1×1卷积(NetworkinNetworkand1×1convolutions)2.6谷歌Inception网络简介(Inceptionnetworkmotivation)......
  • 2024-2025-1 20241422《计算机基础与程序设计》第一周学习总结
    这个作业属于哪个课程2024-2025-1-计算机基础与程序设计)这个作业要求在哪里2024-2025-1计算机基础与程序设计第一周作业)这个作业的目标快速浏览一遍教材计算机科学概论(第七版),课本每章提出至少一个自己不懂的或最想解决的问题并在期末回答这些问题作业正文2024-......
  • 深度学习中的结构化概率模型 - 引言篇
    序言在深度学习的广阔领域中,结构化概率模型(Structured Probabilistic Models\text{StructuredProbabilisticModels}Structured Probabilistic Models)扮演着至关重......
  • 学期:2024-2025-1 学号:20241406《计算机基础与程序设计》第1周学习总结
    |这个作业属于哪个课程||https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP|这个作业要求在哪里|https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP/homework/13276|这个作业的目标|课程概论,工业革命与浪潮之巅,信息与信息安全,计算机系统概论,计算机安全,计算机的限......
  • 2024-2025-1 20241319 《计算机基础与程序设计》第一周学习总结
    作业信息这个作业属于哪个课程2024-2025-1-计算机基础与程序设计这个作业要求在哪里2024-2025-1计算机基础与程序设计第一周作业这个作业的目标锻炼我们的自主学习能力,让我们对接下来的学习有一个基本了解,同时也让我们能更加迅速、深入地学习计算机相关内容作业......
  • 2024-2025-1 学号20241306《计算机基础与程序设计》第一周学习总结
    2024-2025学期20241306《计算机基础与程序设计》第1周学习总结这个作业属于哪个课程<班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里2024-2025-1计算机基础与程序设计第一周作业](https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP/homewor......
  • Apache学习笔记(详解,漏洞复现,基线检查)
    目录:Apache详解一、配置文件详解1、默认配置2、访问控制和网站首页配置3、日志文件配置4、类型配置二、作业+基于目录的访问控制三、Apache设置虚拟主机APAche漏洞复现一、Apache多后缀解析漏洞1、后缀解析的原理:2、漏洞复现3、漏洞防御二......
  • 学期2024-2025-1 学号 20241317 《计算机基础与程序设计》第1周学习总结
    这个作业属于哪个课程 https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP这个作业要求在哪里 https://www.cnblogs.com/rocedu/p/9577842.html#WEEK01这个作业的目标 1.基于VirtualBox虚拟机安装Ubuntu和安装Linux系统2.快速浏览一遍教材计算机科学概论(第七版),课本每章提......
  • 2024-2025-1 20241301 《计算机基础与程序设计》第4周学习总结
    这个作业属于哪个课程<[2024-2025-1-计算机基础与程序设计](https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP)>这个作业要求在哪里<2024-2025-1计算机基础与程序设计第一周作业>这个作业的目标<总结一周内的学习内容,巩固所学,加深对计算机学科的理解,提升思考能......
  • EasyExcel导出文件基本流程以及原理分析 学习笔记(持续更新)
    EasyExcel导出文件基本流程导出文件基本流程获取数据首先获得需要导出的文件的数据内容,用一个list保存List<SysStudent>list=sysStudentService.queryList(sysStudent);定义文件名给导出的文件定义一个名字,可以添加日期或者根据输入添加其他信息,保证文件名唯一S......