Java从入门到精通(十四) ~ 多线程

 晚上好,愿这深深的夜色给你带来安宁,让温馨的夜晚抚平你一天的疲惫,美好的梦想在这个寂静的夜晚悄悄成长。

目录

前言

一、多线程是什么?

Java中的多线程 

二、使用步骤

1.创建方式

1.1 Thread 线程

1.2 Runnable 任务

1.3 Callable 带返回值任务

Callable的底层原理

run方法底层会转调call()方法也就代码中重写的

如果任务尚未完成,调用 get() 方法会阻塞当前线程,直到任务完成并返回结果:

创建带返回值的任务:

代码执行: 

Runnable 和 Callable 的区别

1.4 使用Executor框架

2. 常用api

1. 启动线程:

2. 线程生命周期管理:

3. 线程状态控制:

4. 线程同步与通信:

5. 设置线程名称: 

6. 设置线程优先级: 

7.守护线程(Daemon Thread)

创建守护线程的方法

特点和注意事项

3.自己对多线程理解

1. 线程并不是同时创建的

2. 并不是main线程在等其他线程执行完毕后,程序才结束

 三、线程的六大状态

1. 概念

2. 线程状态之间是如何变化的

 3. 为什么运行状态不属于线程的状态?

四、线程池 

1. 介绍

2. 线程池的优势:

3. 自定义线程池 

添加线程次序 

关于拒绝策略的选择:

总结


前言

        在软件开发中,多线程编程是一项重要的技能,特别是在处理并发问题和提升程序性能方面起到关键作用。本文将探讨Java中多线程编程的基础知识、常见应用场景以及一些最佳实践。

当多线程切换时间片的速度非常快时,会给人一种多个任务同时进行的感觉。比如,视频播放实际上是由一帧一帧的图片组成的,但由于切换速度很快,用户无法感知到图片之间的切换。这种体验使得用户可以在玩游戏的同时听歌、收到QQ消息,极大地提升了用户的体验。因此,多线程的重要性不言而喻。

一、多线程是什么?

        多线程是指一个程序中包含多个执行流,并行执行各自独立的任务。在单核处理器上,多线程通过时间片轮转实现并发执行;在多核处理器上,多线程可以同时执行多个任务,充分利用多核资源提升程序性能。 

Java中的多线程 

对于Java的mt.start()会开辟一个栈内存,不同栈内存开始抢夺cpu的执行权。

记住每块线程都自己的栈空间,程序中所有的栈空间不运行完毕,就不会结束程序。

二、使用步骤

1.创建方式

1.1 Thread 线程

通过继承Thread类来创建线程是最基本的方式之一。这种方式需要定义一个新的类,直接继承自Thread,并重写其中的run方法来定义线程的任务逻辑。

class MyThread extends Thread {public void run() {// 线程执行的任务逻辑}
}// 创建并启动线程
MyThread thread = new MyThread();
thread.start();

特点和适用场景

  • 简单直观:继承Thread类易于理解和实现。
  • 适合简单的线程逻辑:当线程的任务逻辑相对独立且简单时,可以考虑使用此方法。

注意事项

  • Java是单继承的:因此如果已经继承了其他类,则无法再通过继承Thread来创建线程。

1.2 Runnable 任务

实现Runnable接口是更加灵活的一种创建线程的方式。这种方法将任务逻辑封装在实现了Runnable接口的类中,并将其传递给Thread类的构造函数。

Thread底层:

使用一个成员变量存储传参的Runnable对象,然后底层的run方法会调用Runnable的run()方法

代码实现: 

class MyRunnable implements Runnable {public void run() {// 线程执行的任务逻辑}
}// 创建线程并启动
Thread thread = new Thread(new MyRunnable());
thread.start();

特点和适用场景

  • 可继承性:实现Runnable接口不影响类的继承关系,可以更灵活地管理线程任务。
  • 推荐的方式:在大多数情况下,推荐使用实现Runnable接口的方式来创建线程,因为它更加面向接口编程,符合面向对象设计原则。

注意事项

  • 线程安全:在多个线程访问共享资源时需要注意线程安全性。

1.3 Callable 带返回值任务

实现Callable接口可以获取返回值是更加灵活的一种创建线程的方式。这种方法将任务逻辑继承了Runnable接口的类中,并在通过get()获取返回值,底层会阻塞其他线程直到当前线程执行执行完毕并返回返回值,才会继续执行后续代码。

futureTask.get();获取子线程的任务,如果子线程没有返回结果,那么我就一直等等等(阻塞),直到返回结果为止! 

Callable的底层原理

run方法底层会转调call()方法也就代码中重写的

如果任务尚未完成,调用 get() 方法会阻塞当前线程,直到任务完成并返回结果:

 禁用其他线程,一直等待当前线程返回结果为止

创建带返回值的任务:
package demo2;import javax.security.auth.callback.Callback;
import java.util.concurrent.Callable;/*** @author windStop* @version 1.0* @description 带回调的任务* @date 2024年07月25日19:58:29* async:函数是异步函数 await:等待某个函数执行完毕后,继续执行 ---Promise*/
public class MyCallback implements Callable<Long> {//线程任务@Overridepublic Long call() throws Exception {long sum = 0;for (int i = 0; i < 100000; i++) {sum+=i;}System.out.println("MyCallable.run 输出: "+sum);return sum;}
}
代码执行: 
package demo2;import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** @author windStop* @version 1.0* @description 测试:带回调的任务* @date 2024年07月25日20:00:42*/
public class Test2 {public static void main(String[] args) throws ExecutionException, InterruptedException {//带回调任务的线程//FutureTask是Runnable接口的实现类//FutureTask中提供了一个方法 get方法,用于获取未来任务执行完毕后返回的结果FutureTask<Long> futureTask = new FutureTask<>(new MyCallback());Thread t = new Thread(futureTask);t.start();//获取子线程执行的结果,一定在开启线程之后获取Long l = futureTask.get();//获取子线程的任务,如果子线程没有返回结果,那么我就一直等等等(阻塞),直到返回结果为止!System.out.println("结果:" + l);new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "爱李白");}}).start();}}

 好处:

  1. 返回结果Callable接口的主要优势在于它可以返回一个结果,这个结果可以在任务执行完成后通过Future对象获取。这使得在多线程任务中,可以方便地获取并处理任务的执行结果,而不需要使用共享变量或者其他的线程同步机制。

  2. 异常处理Callablecall()方法可以抛出异常,与Runnable不同,Runnable的run()方法只能在内部捕获异常并处理,无法向外抛出。在实际应用中,能够处理任务执行过程中可能抛出的异常是非常重要的,Callable能够提供更灵活的异常处理机制。

Runnable 和 Callable 的区别

  1. 返回值类型

    • Runnable 接口的 run() 方法没有返回值,因此不能返回执行结果。
    • Callable 接口的 call() 方法有返回值,可以返回执行结果。返回值类型通过泛型指定,在调用时可以获取到异步执行的结果。
  2. 异常抛出

    • Runnable 接口的 run() 方法无法抛出 checked 异常(即需要在方法签名中声明的异常),只能在方法内部进行捕获和处理,不能将异常向外抛出。
    • Callable 接口的 call() 方法可以抛出异常(包括 checked 异常),允许将异常传播到调用者处,由调用者进行处理。
  3. 使用场景

    • Runnable 通常用于需要线程执行任务但不需要返回结果的情况,例如简单的线程任务执行或异步处理。
    • Callable 通常用于需要线程执行任务并且能够获取执行结果的情况,例如在多线程计算中提交任务并获取计算结果,或者需要处理异常情况的任务。

1.4 使用Executor框架

Java提供了Executor框架来管理线程的生命周期和执行任务。Executor框架通过ThreadPoolExecutor类实现线程池,可以重用线程并提供更高的灵活性和性能。

ExecutorService executor = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池
executor.submit(new MyRunnable()); // 提交任务给线程池执行
executor.shutdown(); // 关闭线程池

特点和适用场景

  • 线程池管理:通过Executor框架可以有效地管理和调度大量线程。
  • 提高性能:线程池可以重用线程、减少线程创建和销毁的开销,提高系统性能。

注意事项

  • 适当的线程池大小:需要根据实际情况选择合适的线程池大小,避免资源浪费或者任务阻塞。

2. 常用api

1. 启动线程

  • start()方法:调用Thread对象的start()方法来启动线程,实际上会调用线程的run()方法来执行任务。

在Java中,一旦一个线程被启动后就不能再次调用它的start()方法。如果尝试多次调用start()方法会导致IllegalThreadStateException异常的抛出。

run()和 start()有什么区别?

  • start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
  • run(:封装了要被线程执行的代码,可以被调用多次。

2. 线程生命周期管理

                join()方法等待指定线程执行完成。当前线程会阻塞直到目标线程执行完成。

thread.join(); // 等待thread线程执行完成

3. 线程状态控制

  • sleep()方法:使当前线程暂停执行一段时间,让出CPU给其他线程。

    try {Thread.sleep(1000); // 暂停当前线程1秒
    } catch (InterruptedException e) {e.printStackTrace();
    }
    
  • yield()方法:暗示当前线程愿意让出CPU执行时间,但是实际是否让出取决于线程调度器的实现。

    实际没啥用,你让了并不代表可以抢到。

4. 线程同步与通信

  • synchronized关键字:用于实现线程的同步,可以修饰方法或代码块,保证多个线程对共享资源的安全访问。同步锁

    synchronized void synchronizedMethod() {// 线程安全的代码块
    }
    
  • Synchronized【对象锁】采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。

  • wait()、notify()、notifyAll()方法:与synchronized关键字一起使用,实现线程的等待和唤醒机制,用于线程间的协作。

  • notify():随机唤醒一个在wait()的线程
    notifyAll():唤醒所有在wait的线程

    synchronized (sharedObject) {sharedObject.wait(); // 线程等待sharedObject.notify(); // 唤醒一个等待的线程sharedObject.notifyAll(); // 唤醒所有等待的线程
    }
    

5. 设置线程名称: 

可以通过setName()方法设置线程的名称,使得线程在日志和调试中更易于识别

Thread thread = new Thread(new Runnable() {public void run() {// 线程执行的任务逻辑}
});
thread.setName("BackupThread"); // 设置线程名称

6. 设置线程优先级: 

  • Java中线程优先级范围是1到10,其中1为最低优先级,10为最高优先级。可以使用setPriority()方法设置线程的优先级。main线程和默认线程优先级是5
thread.setPriority(Thread.MIN_PRIORITY); // 设置线程优先级为最低优先级
// 或者
thread.setPriority(Thread.MAX_PRIORITY); // 设置线程优先级为最高优先级

7.守护线程(Daemon Thread)

在Java中,线程可以分为两种类型:用户线程和守护线程。

  • 用户线程(Non-Daemon Thread)是程序的主要执行线程,当所有的用户线程结束时,程序会退出。
  • 守护线程(Daemon Thread)则是为其他线程提供服务的线程,当所有的用户线程结束时,守护线程会自动被终止。
创建守护线程的方法

在Java中,可以通过设置线程对象的setDaemon(true)方法将其设置为守护线程。

Thread daemonThread = new Thread(new Runnable() {public void run() {// 守护线程的任务逻辑}
});daemonThread.setDaemon(true); // 将线程设置为守护线程
daemonThread.start(); // 启动守护线程
特点和注意事项
  • 守护线程通常用于在后台提供一些服务或监视其他线程的运行状态。
  • 当所有的非守护线程结束时,守护线程会随之自动结束,即使守护线程尚未执行完其任务。
  • 守护线程不能持有任何会影响JVM退出的资源,例如文件句柄或数据库连接,因为它们可能会在任何时候被强制终止。

8. lock锁

1. 创建Lock对象  

2. lock.lock()开启锁

3. lock.unlock()关闭锁,一般放在finally语句块,防止锁无法被释放


public class LockExample {private final Lock lock = new ReentrantLock();public void performTask() {lock.lock(); // 开启锁try {// 在这里执行需要保护的代码块System.out.println("Lock acquired, performing task...");Thread.sleep(2000); // 模拟执行任务的耗时} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock(); // 确保在任何情况下都释放锁System.out.println("Lock released.");}}

wait和sleep方法的不同?

  • 共同点
    • wait(),wait(long)和 sleep(long)的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
  • 不同点

        1.方法归属不同
                sleep(lonq)是 Thread 的静态方法
                而 wait0),wait(long)都是 Object 的成员方法,每个对象都有

        2.醒来时机不同
                执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
                wait(long)和 wait() 还可以被 notify 唤醒,wait0) 如果不唤醒就一直等下去
                它们都可以被打断唤醒

        3.锁特性不同(重点)

wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

3.自己对多线程理解

线程在执行start的时候会在,栈内存中创建一个栈,与main线程抢夺线程, main线程执行执行到下个start()才会三个线程一起抢夺 当一个线程对象调用 start() 方法时,系统会为该线程分配必要的资源,并将其状态设置为可运行(Runnable)状态。 每个线程都有自己的执行栈(Execution Stack),用于存储方法调用、局部变量和部分运行状态。

1. 线程并不是同时创建的

线程只有执行到start()方法才会创建,并且创建也需要时间,时间非常快,差不多0.07ms,创建完毕后开始和main线程抢夺cpu资源,直到main线程抢夺资源后执行到第二个start()才会三个一起抢夺cpu资源。

2. 并不是main线程在等其他线程执行完毕后,程序才结束

main线程执行完毕后,main线程的栈内存会被释放,然后虚拟机并不会结束的原因是因为还有其他栈内存并没有执行完成。当其他线程执行完毕才会结束程序。

 三、线程的六大状态

1. 概念

Thread有一个枚举内部类,表示着他们的状态

  1. 新建(New)状态

    • 当线程对象被创建但还未启动时,线程处于新建状态。
    • 可以通过创建一个 Thread 对象来实现新建状态,但调用 start() 方法之前,线程不会进入可运行状态。
  2. 可运行(Runnable)状态

    • 当线程已经在 JVM 中创建,但尚未开始执行时,或者线程正在运行中,都属于可运行状态。
    • 在可运行状态下,线程可以开始执行,也可能因为 CPU 时间片用尽而暂时停止执行。
  3. 阻塞(Blocked)状态

    • 线程处于阻塞状态时,它暂时放弃 CPU 并且不会参与调度。
    • 典型的阻塞原因包括等待一个监视器锁、等待输入/输出完成或等待调用某个方法的其他线程执行完毕。
    • 一旦等待的条件满足,线程将会重新进入可运行状态。
  4. 等待(Waiting)状态

    • 线程进入等待状态表示它无限期地等待另一个线程采取某些操作。
    • 线程可以通过调用 Object.wait()Thread.join() 或 LockSupport.park() 方法进入等待状态。
    • 等待状态的线程需要其他线程显式地唤醒,以使其重新进入可运行状态或阻塞状态。
  5. 超时等待(Timed Waiting)状态

    • 当线程在指定时间内等待某一操作完成时,线程进入超时等待状态。
    • 典型的超时等待包括调用 Thread.sleep(long millis)Object.wait(long timeout) 或 Thread.join(long millis) 方法。
    • 当指定的时间到达或等待条件满足时,线程会返回到可运行状态或阻塞状态。
  6. 终止(Terminated)状态

    • 线程已经完成了执行或者因为异常退出了 run() 方法而结束,进入终止状态。
    • 一旦线程进入终止状态,它就不能再次进入可运行状态。

2. 线程状态之间是如何变化的

  • 新建状态 -> 可运行状态:调用 start() 方法启动线程,线程从新建状态转换到可运行状态。
  • 可运行状态 -> 阻塞状态:例如等待某个锁,线程从可运行状态转换到阻塞状态。
  • 阻塞状态 -> 可运行状态:条件满足时,线程从阻塞状态转换回可运行状态。
  • 可运行状态 -> 等待状态或超时等待状态:线程调用 wait()join() 或 sleep() 方法,进入等待或超时等待状态。
  • 等待状态/超时等待状态 -> 可运行状态:其他线程唤醒等待的线程,使其重新进入可运行状态。
  • 可运行状态 -> 终止状态:线程的 run() 方法执行完毕,线程进入终止状态。

 3. 为什么运行状态不属于线程的状态?

线程运行就交给操作系统管理了,属于操作系统管理的内容,线程被选中执行并且正在CPU上执行代码时,它处于操作系统的运行状态。这种运行状态对于Java的线程管理来说并不是一个独立的状态,而是RUNNABLE状态的一部分,包括正在执行和等待执行的情况。

4. 为什么要有锁?

多线程的同步锁是一种重要的机制,用于在多个线程访问共享资源时确保线程安全。 

线程不安全的三大条件:1.多线程  2. 具有公共资源  3 .增删改查共享数据 ,三个条件缺一不可才能满足线程不安全。

对于同步锁就是打破第三点增删改查共享数据,来让线程安全。

就是给一个增删改查的代码进行加锁,我在执行的时候,别的代码不能进来,这就代表只能同时有一个操作完成增删改的内容,就保证了线程的安全。

synchronized 会起到互斥效果 , 某个线程执行到某个对象的 synchronized 中时 , 其他线程如果也执行到同一个对象 synchronized 就会 阻塞等待。
  • 进入 synchronized 修饰的代码块, 相当于 加锁
  • 退出 synchronized 修饰的代码块, 相当于 解锁
可以粗略理解成 , 每个对象在内存中存储的时候 , 都存有一块内存表示当前的 " 锁定 " 状态 ( 类似于厕
所的 " 有人 / 无人 ").
如果当前是 " 无人 " 状态 , 那么就可以使用 , 使用时需要设为 " 有人 " 状态 .
如果当前是 " 有人 " 状态 , 那么其他人无法使用 , 只能排队
理解 " 阻塞等待 ".
针对每一把锁 , 操作系统内部都维护了一个等待队列 . 当这个锁被某个线程占有的时候 , 其他线程尝
试进行加锁 , 就加不上了 , 就会阻塞等待 , 一直等到之前的线程解锁之后 , 由操作系统唤醒一个新的
线程 , 再来获取到这个锁 .

 

四、线程池 

1. 介绍

概念: 使用一个容器(数组|集合),存放了多个线程对象

作用: 提高线程的使用率, 避免创建过多的线程

线程池是一种管理和复用线程的机制,它能够有效地管理大量线程并控制同时执行的线程数量。通过使用线程池,可以避免重复创建和销毁线程所带来的性能开销,并且能够更好地管理系统的并发资源。

比如:线程池就相当于一个碗柜,第一次吃饭需要买碗,买完碗放往柜放,如果没有线程池就相当于,你每次吃完饭都把碗给砸了,下次吃还要继续买碗。

2. 线程池的优势:

  1. 减少资源消耗:重用线程可以减少线程创建和销毁的开销。
  2. 提高响应速度:通过减少线程创建时间,可以更快地响应任务。
  3. 提高线程的可管理性:可以限制并发线程的数量,防止资源耗尽。
  4. 提供更强的功能:线程池提供了任务调度、线程安全、线程超时等功能。

3. 自定义线程池 

public class Test {public static void main(String[] args) {//创建线程池ThreadPoolExecutor executor = new ThreadPoolExecutor(4,//核心线程数量6,//最大线程数量10,//最大存活时间TimeUnit.MINUTES,//时间单位new ArrayBlockingQueue<>(3),//任务队列Executors.defaultThreadFactory(),//线程工厂//new ThreadPoolExecutor.AbortPolicy()//拒绝策略  抛弃任务,报错//new ThreadPoolExecutor.DiscardPolicy()//拒绝策略  抛弃任务new ThreadPoolExecutor.CallerRunsPolicy()//拒绝策略, 抛弃任务,那个线程提交的任务,该线程执行这个任务);//2.把任务交给线程池 submit(Runnable r)for (int i = 0; i < 10; i++) {executor.submit(()->{System.out.println(Thread.currentThread().getName()+" 我是你大爷...");});}//关闭线程池//executor.shutdown();}
}

  1. corePoolSize

    • 核心线程池大小为 4。在没有设置 allowCoreThreadTimeOut 的情况下,即使线程处于空闲状态,也不会被回收,始终保持在这个数量。
  2. maximumPoolSize

    • 最大线程池大小为 6。当线程池中的线程数量超过 corePoolSize,并且任务队列已满时,线程池会创建新的线程,但不会超过这个最大值。
  3. keepAliveTime

    • 线程的最大存活时间为 10 分钟。超过核心线程数的空闲线程在此时间后会被回收,以减少资源消耗。
  4. unit

    • keepAliveTime 的时间单位为 TimeUnit.MINUTES,即分钟。
  5. workQueue

    • 使用 ArrayBlockingQueue 作为任务队列,容量为 3。如果线程池中的线程数量超过 corePoolSize,多余的任务会被放入这个队列中等待执行。
  6. threadFactory

    • Executors.defaultThreadFactory() 是默认的线程工厂,用来创建新的线程。可以通过自定义线程工厂来设置线程的名称、优先级等。
  7. handler

    • ThreadPoolExecutor.CallerRunsPolicy() 是一种拒绝策略,用于处理当任务无法加入到队列中时的情况。这个策略会让调用者线程自己执行该任务,这样一来,提交任务的线程不会被阻塞,而是由调用线程直接执行任务。

添加线程次序 

首先添加到核心线程中,核心线程满了,会添加到任务队列里面,任务队列满了,才会添加到临时线程里面,临时线程满了,才会触发拒绝策略。对于临时线程会keepAliveTime时间到了还没有被使用就会被摧毁。

关于拒绝策略的选择:

  • AbortPolicy:默认的拒绝策略,会抛出 RejectedExecutionException 异常。
  • DiscardPolicy:直接丢弃无法处理的任务,不会抛出异常。
  • CallerRunsPolicy:让提交任务的线程自己执行这个任务,不会抛弃任务,也不会抛出异常。
  • DiscardOldestPolicy:丢弃队列中等待时间最长的任务,然后尝试重新提交当前任务。

总结

多线程编程在Java开发中扮演着重要角色,能够提升程序的并发能力和响应速度,有效地管理和利用系统资源。通过理解线程的创建方式、生命周期、状态转换以及线程池的使用,开发者可以更好地设计和实现复杂的并发应用程序,提高系统的稳定性和性能。

综上所述,掌握Java多线程编程的基础知识,并结合实际场景应用,将极大地提升软件开发的效率和质量。

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

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

相关文章

iOS ------ KVO KVC

一&#xff0c; KVO KVO介绍 KVO全称KeyValueObserving,俗称键值监听&#xff0c;是苹果提供的一套时事件通知机制。允许对象监听另一个对象特定属性的改变&#xff0c;并在改变时接受事件。一般继承自NSObject的对象都默认支持KVOKVO和NSNotificationCenter都是iOS观察者模式…

MySQL基础练习题11-换座位

题目&#xff1a;交换每两个连续的学生的座位号。如果学生的数量是奇数&#xff0c;则最后一个学生的id不交换。按 id 升序 返回结果表。 准备数据 分析数据 方法一&#xff1a;利用power函数对id进行交换&#xff0c;得出的答案只有0或1 第一步&#xff1a;用power()函数将…

公司常用的监控软件有哪些?2024年六大公司监控软件良心推荐!

在现代企业管理中&#xff0c;监控软件不仅可以帮助提高员工生产力&#xff0c;还可以确保企业数据的安全和保护。小编分享六款公司监控软件&#xff0c;能够满足不同企业的需求&#xff0c;提升管理效率和信息安全。 一、值得推荐的监控软件 1. 固信软件 固信软件https://ww…

【软件测试】--接口测试

1. 接口用例设计 接口测试的测试点 功能测试 单接口功能&#xff1a; 手工测试中的单个业务模块&#xff0c;一般对应一个接口 登陆业务 --> 登陆接口加入购物车业务 --> 加入购物车接口订单业务 --> 订单接口支付业务 --> 支付接口 借助工具、代码。绕开前端界面…

【初阶数据结构题目】1.返回倒数第k个节点

文章目录 题目描述代码 题目描述 返回倒数第k个节点 代码 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/typedef struct ListNode ListNode; int kthToLast(struct ListNode* head, int k){ListNode* t hea…

域名未备案可以申请SSL证书吗??

域名未备案可以申请SSL证书。SSL证书的申请并不依赖于域名的备案情况&#xff0c;而是在于对域名的掌握权。只要你拥有域名的所有权&#xff0c;即具有对域名管理的权限&#xff0c;就可以在任何时候向认证机构申请SSL证书。 申请SSL证书的流程通常包括以下几个步骤&#xff1a…

做前端4年了,才明白技术的本质不过是工具而已

四年前&#xff0c;我踏上了前端开发的道路&#xff0c;从HTML和CSS到JavaScript&#xff0c;从jQuery到React&#xff0c;每一步都走得踏实而坚定。随着经验的积累&#xff0c;技术的进步&#xff0c;我逐渐认识到&#xff0c;所谓的“技术”&#xff0c;无非是实现目标的一种…

颜色识别基于高斯混合模型(GMM)的查找表分类器(LUT)

文章目录 create_class_gmm 创建高斯混合模型&#xff08;GMM&#xff09;以进行分类任务add_samples_image_class_gmm 提取训练样本&#xff0c;并将其添加到高斯混合模型 (GMM) 的训练数据集中train_class_gmm 训练一个高斯混合模型 (GMM)clear_class_gmm 清除模型create_cla…

Fiddler学习笔记

目录 前言 简介 原理 界面 前言 测试可以使用fiddler工具&#xff0c;通过抓包的方式修改前端参数和模拟后端返回&#xff0c;快速定位缺陷。 简介 Fiddler是HTTP协议调试代理工具&#xff0c;可以记录并检查所有客户端和服务器之间的HTTP和HTTPS请求&#xff0c;允许监视…

QT报红色错误,实际可以编译

QT报红色错误&#xff0c;实际可以编译&#xff0c;看着难受&#xff0c;如何去掉报警 进入插件 勾选框去掉&#xff0c;然后重启QT

Java——循环控制for,while,do...while

目录 1.for循环控制 基本介绍 基本语法 流程分析 案例演示&#xff1a; 注意事项和细节说明 练习题 2.while循环控制 基本语法 流程图 案例演示1 注意事项和细节说明 案例演示2 ​3.do...while循环控制 基本语法 说明 流程图 注意事项和细节说明 练习题…

杂项运算符及运算符的优先级

文章目录 常见的杂项运算符运算符的优先级特殊运算符运算符重载运算符的结合性实际应用中的注意事项1. 空条件运算符 (Null Coalescing Operator)JavaScript 示例: 2. 范围运算符 (Range Operator)Swift 示例: 3. 模式匹配运算符 (Pattern Matching)Rust 示例: 4. 解构赋值运算…

C# 12 新增功能实操!

前言 今天咱们一起来探索并实践 C# 12 引入的全新功能&#xff01; C#/.NET该如何自学入门&#xff1f; 注意&#xff1a;使用这些功能需要使用最新的 Visual Studio 2022 版本或安装 .NET 8 SDK 。 主构造函数 主构造函数允许你直接在类定义中声明构造函数参数&#xff0c;…

从零开始编写一个Chrome插件:详细教程

个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119@qq.com] 📱个人微信:15279484656 🌐个人导航网站:www.forff.top 💡座右铭:总有人要赢。为什么不能是我呢? 专栏导…

Pytorch笔记1

建议点赞收藏关注&#xff01;持续更新至pytorch大部分内容更完。 整体框架如下 目录 gpu加速数据数据结构张量TensorVariable 预处理数据增强 模型构建模块组织复杂网络初始化网络参数定义网络层 损失函数创建损失函数设置损失函数超参数选择损失函数 优化器管理模型参数管理…

“八股文”:程序员的福音还是梦魇?

——一场关于面试题的“代码战争” 在程序员的世界里&#xff0c;“八股文”这个词儿可谓是“如雷贯耳”。不&#xff0c;咱们可不是说古代科举考试中的那种八股文&#xff0c;而是指程序员面试中的那些固定套路的题目。如今&#xff0c;各大中小企业在招聘程序员时&#xff0…

Overlay网络

Overlay 介绍 Overlay网络是将已有的物理网络&#xff08;Underlay网络&#xff09;作为基础&#xff0c;在其上建立叠加的逻辑网络&#xff0c;实现网络资源的虚拟化。 传统网络带来了以下一些问题&#xff1a; ● 虚拟机规模受 网络规格限制在传统二层网络环境下&#xff0…

泰迪智能科技大数据实验室——陕西省高校合作成功案例

近年来&#xff0c;陕西省紧跟国家大数据发展战略&#xff0c;积极推进大数据产业发展。在政策扶持、产业布局、技术创新等方面取得显著成效。泰迪智能科技大数据实验室立足陕西&#xff0c;携手西安邮电大学、西安财经大学、陕西科技大学镐京学院、宝鸡文理学院、渭南师范学院…

使用Selenium爬虫批量下载AlphaFold数据库中的PDB文件

注意&#xff1a;本方法使用了python&#xff0c;下载速度一般&#xff0c;如果需要更快的大批量下载可以考虑使用其他方法&#xff0c;例如FTP Alphafold数据库其实提供了许多物种的蛋白质组&#xff1a; AlphaFold Protein Structure Database 但是如果你搜索的物种不在这个…

【从0制作自己的ros导航小车:上位机篇】02、ros1多机通讯与坐标变换可视化

从0制作自己的ros导航小车 前言一、ros1多机通讯二、rviz可视化小车坐标系 前言 上节课完成了里程计数据与坐标变换发布&#xff0c;但是还没有测试&#xff0c;本节进行测试&#xff0c;测试之前需要知道一件事&#xff0c;上位机也就是开发板一般不做可视化用&#xff0c;因…