java基础--多线程学习

news2024/11/30 6:54:00
写在前面:
多线程在面试中问的很多,之前没有过系统的学习,现在来进行一个系统的总结学习

文章目录

  • 基础
  • java多线程实现
    • 无参无返回值线程
      • 快速创建
      • start和run方法的探讨
      • run方法
      • 线程状态
    • 有返回值线程
    • 线程池执行
    • 小结
      • 关于抛出异常的扩展
  • 线程方法
    • 线程名称
    • 获取当前线程
    • 线程休眠
    • 中断
    • 停止
    • 优先级
      • main线程
    • 守护线程
    • 礼让线程以及插入线程
  • 线程安全问题
    • synchronized
      • 对象锁
      • 方法锁
    • Lock
  • 死锁问题
  • 等待唤醒机制
    • 经典问题生产者消费者问题
        • 生产者唤醒不会唤醒生产者吗
        • 阻塞队列实现
  • 线程池
    • 线程池参数
    • 执行过程
      • 认为拒绝策略
    • 线程池创建
    • 执行线程
      • 最佳线程池大小
  • volatile
    • jit优化
    • 指令重排序

基础

cpu就像一个小公司一样,人力就类似于执行开销。
进程类似于小公司的一个组,像在win系统中,qq,微信等一个软件就是一个进程。(一个软件的基础执行整体)
线程就像组中的每个人,每个人都可以同时去做不同的事。

有了多线程就相当于可以让程序同时做很多事。

并行与并发:

  • 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
  • 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。就是同一时刻只能做一件事,但是由于其一下做这个事一下做另一个事,在某个时间段就像是同时做一样。这么说这是因为我们感知的时间可能是毫秒,而其每件事做一下的时间在微秒,所以在感知上是同时做。

java多线程实现

在java中和线程打交道最多的就是Thread类了。其线程相关方法在底层基本上用的是native修饰的,也就是说不需要程序员来考虑,由虚拟机提供。
下面是jdk中thread类的类注释对线程的介绍
在这里插入图片描述

无参无返回值线程

在thread类的类注释上有着2中创建多线程的方式。一种是继承Thread类,另一种是重写runable接口然后传递给thread类。
在这里插入图片描述

上面写过的就不赘述了。

快速创建

在Runable接口上有着FunctionalInterface的注解,那么我们就可以通过lambda表达式快速的新建一个线程来执行。
在这里插入图片描述

start和run方法的探讨

start的方法
多次启动一个线程是不合法的。特别是,线程一旦完成执行,就不能重新启动。

既然是重写的run方法,那直接调用run方法有没有用呢。
查看run方法源码,会发现没线程相关的内容,只是简单的调用方法。

在这里插入图片描述
而start方法,会调用一个叫start0的本地方法,由虚拟机去创建线程。
在这里插入图片描述
在start方法上我们发现了synchronized关键字,那么说明这个方法是互斥的,不能同时执行。

run方法

所以说Thread有2种方法,如果继承了Thread重写了run方法,就是重写的逻辑,如果没有重写,就需要传递一个target(也就是runable的实现类)

线程状态

threadStatus这个是名字应该是表示的线程的状态。
通过搜索我们发现了其get的方法,深入进入
在这里插入图片描述
可以看到一堆枚举
在这里插入图片描述
这里就是线程的状态了,注释上面写的挺清楚的就不赘述了。至于这个的具体数值应该不用探索。
在这里插入图片描述
在这里插入图片描述

有返回值线程

线程任务
继承callable 泛型就是返回值的类型

import java.util.concurrent.Callable;
import java.util.stream.IntStream;

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = IntStream.range(1, 5000).sum();
        System.out.println(sum);
        return sum;
    }
}

main方法

    public static void main(String[] args) {
        // 创建callable对象
        MyCallable mc = new MyCallable();
        // 创建FutureTask对象
        FutureTask<Integer> ft = new FutureTask<>(mc);
        // 创建线程对象
        Thread t = new Thread(ft);
        // 启动线程
        t.start();
        try {
            // 获取线程执行结果
            System.out.println(111);
            Integer sum = ft.get();
            System.out.println(222);
            System.out.println("1-5000的和为:" + sum);
            System.out.println(333);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

执行结果,不过奇怪的是这个真的开了线程吗?执行像是顺序一样。
在这里插入图片描述
改变代码

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        Thread.sleep(3000);
        int sum = IntStream.range(1, 5000).sum();
        System.out.println(sum);
        return sum;
    }
}

public static void main(String[] args) {
        // 创建callable对象
        MyCallable mc = new MyCallable();
        // 创建FutureTask对象
        FutureTask<Integer> ft = new FutureTask<>(mc);
        // 创建线程对象
        Thread t = new Thread(ft);
        FutureTask<Integer> ft2 = new FutureTask<>(()->{
            System.out.println("ft2");
            return 1;
        });
        Thread t2 = new Thread(ft2);
        t.start();
        t2.start();
        try {
            // 获取线程执行结果
            System.out.println(111);
            Integer sum = ft.get();
            System.out.println(222);
            Integer i = ft2.get();
            System.out.println(i);
            System.out.println("1-5000的和为:" + sum);
            System.out.println(333);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

结果可以看出来get应该是有阻塞的作用的。
在这里插入图片描述
main方法修改得到

try {
            // 获取线程执行结果
            System.out.println(111);
            try {
                Integer sum = ft.get(2000, TimeUnit.MILLISECONDS);
                System.out.println("1-5000的和为:" + sum);
            }catch (Exception e){
                System.out.println("超时");
            }
            System.out.println(222);
            Integer i = ft2.get();
            System.out.println(i);
            System.out.println(333);
        } catch (Exception e) {
            e.printStackTrace();
        }

结果,可以发现知道超时才会停止阻塞
在这里插入图片描述

线程池执行

这种方式实现也简单,而且我发现程序是不会停止的,需要使用下面方法才会

        executorService.shutdown();

在这里插入图片描述

小结

三种方式中

方式简介
继承Thread编程简单,扩展性差,无法继承其他类了,无返回值,只有他可以直接获取Thread类的方法。
实现runable方法复杂一点,但是如果用内部类能简化代码,扩展性好,可以继承其他类,无返回值,不能抛出异常
实现callable方法有返回值,扩展性好,可以抛出异常。

关于抛出异常的扩展

因为都是实现或者重写方法,但是只有callable的方法抛出了Exception异常。
根据java的规则,子类无法抛出比父类更大的异常,所以无法抛出异常。
在这里插入图片描述
在这里插入图片描述

线程方法

线程名称

在这里插入图片描述
如果没有设置名字,会有默认名称,默认为Thread-加上匿名线程编号,从0开始
在这里插入图片描述
在这里插入图片描述
其次构造也可以起名称,且都不能传递null
在这里插入图片描述

获取当前线程

在这里插入图片描述

线程休眠

这个睡眠的2个参数的方法就有意思了,他的处理只是millis+1,所以额外在哪里去了,随机是吧。
在这里插入图片描述

中断

如果不是正常运行状态则直接中断线程

如果是wait/join/sleep/LockSupport.park等方法调用产生的阻塞状态时,调用interrupt方法,会抛出异常InterruptedException,同时会清除中断标记位,自动改为false。

在这里插入图片描述

查看线程是否中断
在这里插入图片描述

停止

强制停止,已经弃用
在这里插入图片描述

一般使用代码标志位判断,即在代码中用某个变量作为是否停止的标记。

优先级

设置和获取
在这里插入图片描述
可以发现其是有最大和最小值的,最大1最小10默认5,越大优先级越高,抢到cpu的执行权能概率更高
在这里插入图片描述

main线程

额外的我们来看看main线程的消息,优先级5,名称name,id为1
在这里插入图片描述

守护线程

守护线程,当其他非守护线程的结束后守护线程会陆续结束
当不设置守护的时候
在这里插入图片描述

当设置为守护的时候,t1线程没有打印到99
在这里插入图片描述
注意守护线程也不是马上就结束了而是其他非守护线程执行完后陆续结束。

礼让线程以及插入线程

这2
礼让线程,让出cpu的执行权。但是让出后其仍然可以争取cpu的执行权,所以还有可能是其本身继续执行。
谁礼让就执行下面代码就可以了

Thread.yield();

插入线程
让其他线程插入到线程之前,我们来在守护线程的案例上进行。
开始的守护互相抢
在这里插入图片描述
插入后直到守护线程执行完才继续执行
在这里插入图片描述

线程安全问题

当线程穿插的时候很容易出现问题,如

1.if(i>1)
2.   i--

就这样简单的代码,当i=1的时候,a线程执行1通过,b线程执行1通过,此时2个代码都会执行i–操作。
这就是多线程导致的不安全,其原因是判断和执行不能保证原子性。所以我们一般可以使用加锁的方式。

在这里插入图片描述

锁一般分为2种

  • 乐观锁:认为冲突不一定会时刻发生,对于数据冲突保持一种乐观态度。
    具体解决:通过一些业务的校验来判断是否成功。
    如上面对i的校验,判断的时候记录i的值为1,然后在执行减减操作的时候判断i是否等于1,如果等于1才执行(当然判断和执行的时候必须保证其原子性)。
    在数据库里面就可以这么写
update table set i = i - 1 where i = 1;
  • 悲观锁:认为冲突一定会发生,对于数据冲突保持一种悲观态度。
    执行的时候每次都会加锁,保证其执行完后下一个进程才能执行。

synchronized

对象锁

线程同步机制的语法是:

synchronized(){
	// 线程同步代码块
}

小括号种代表加锁的对象,而且执行起来明显速度更慢了
在这里插入图片描述

速度慢了10倍
在这里插入图片描述
在这里插入图片描述
如果开的锁不一样则没有意义,只有相同锁的才会互斥。

方法锁

加在方法上,表示每次只有一个可以调用这个方法,
在这里插入图片描述
这里,synchronized加锁的对象为当前静态方法所在类的Class对象。

如果表示具体方法,synchronized加锁的对象就是这个方法所在实例的本身。即一个对象一个锁。

Lock

lock和synchronized差不多,不过一个对象就相当于一把锁。
在这里插入图片描述

所以说不能把lock变成局部变量,一人一把锁没有意义。
在这里插入图片描述

死锁问题

互相枪锁,这部分在数据库原理的挺多的。a占有1资源,b占有2资源,而a想抢占2资源,b想占1资源导致a,b都无法执行下去。

互相不在继续执行
在这里插入图片描述
在这里插入图片描述

等待唤醒机制

等待唤醒不是在Thread类中而是在Object里面定义的
在这里插入图片描述
notify随机唤醒一个
notifyAll唤醒所有,一起抢cpu
wait阻塞,让活动在当前对象的线程无限等待(释放之前占有的锁)

经典问题生产者消费者问题

生产者不断的产生食物,知道满
消费者不断的消耗食物,直到空

static final ArrayList<Integer> list = new ArrayList<>(10);

    public static void main(String[] args) throws Exception {
        Thread producer = new Thread(() -> {
            while (true) {
                synchronized (list) {
                    try {
                        if (list.size() == 10) {
                            System.out.println("List is full");
                            list.wait();
                        }
                        Thread.sleep(100);
                        list.add(1);
                        System.out.println("Added 1");
                        list.notifyAll();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        Thread consumer = new Thread(() -> {
            while (true) {
                synchronized (list) {
                    try {
                        if (list.isEmpty()) {
                            System.out.println("List is empty");
                            list.wait();
                        }
                        Thread.sleep(100);
                        list.remove(0);
                        System.out.println("Removed 1");
                        list.notifyAll();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        producer.start();
        consumer.start();
    }

执行结果
在这里插入图片描述

生产者唤醒不会唤醒生产者吗

这里只有2个线程一个生产者一个消费者,最后的notifyAll会唤醒另一个,但是如果没满/没空的时候,他还是会去争抢锁的。

多生产者消费者线程呢?

如果有多个生产者线程,每个生产者线程都调用notifyAll()方法,那么会唤醒所有等待在同一个对象上的消费者线程和生产者线程。也就是说,所有等待在该对象上的线程都会被唤醒。如果只想唤醒消费者线程,可以考虑使用不同的对象来进行等待和唤醒操作。

阻塞队列实现
static final ArrayBlockingQueue<Integer> list = new ArrayBlockingQueue<>(10);

    public static void main(String[] args) throws Exception {
        Thread producer = new Thread(() -> {
            while (true) {
                try {
                    list.put(1);
                    Thread.sleep(100);
                    System.out.println("Added 1");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread consumer = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(100);
                    list.poll();
                    System.out.println("Removed 1");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        producer.start();
        consumer.start();
    }

执行结果
在这里插入图片描述

线程池

如果不使用线程池,那么每次都会新建一个线程执行完后销毁,非常的浪费资源。
这就是线程池存在的必要了。

线程池里最开始是没有的,来一个线程创建一个,执行完后也不会销毁,会执行后面的。当然配置有挺多的。

线程池参数

  • 核心线程数:常驻的线程数量,创建后不销毁
  • 最大线程数量:线程池的线程数量可以超过核心线程数但是不能超过最大线程数量,但是超过核心线程数的临时线程,如果线程空闲将会被销毁
  • 空闲时间:空闲超过多久后销毁
  • 空闲时间单位
  • 阻塞队列
  • 创建线程的方式
  • 认为拒绝策略:过多的处理方案

执行过程

先创建核心线程数,多出的放到阻塞队列,阻塞队列满了后,后面来的会创建临时线程进行处理。(所以先提交的不一定先执行)如果还是满了就会按照认为拒绝策略方案进行

认为拒绝策略

  • AbortPolicy:丢弃任务并抛出异常,默认策略
  • DiscardPolicy:丢弃任务不抛出异常
  • DiscardOldestPolicy:抛弃等待时间最长的任务,然后加入
  • CallerRunsPolicy:直接执行run方法

线程池创建

我们在上面使用了Executors创建的线程池,这样方便。
我们来学习一下。
Executors是通过其静态方法来创建线程池,其内部是通过ThreadPoolExecutor的全参构造进行的,所以学习了ThreadPoolExecutor的构造其他的看一眼源码就知道了。
这些就是上面讲到的线程池参数
在这里插入图片描述
newFixedThreadPool,fixed固定,创建的是固定大小的线程池,没有临时线程,LinkedBlockingQueue最大为int的最大整数,AbortPolicy为默认拒绝策略

在这里插入图片描述
工厂采用的是默认的,采用new Thread,优先级为5的
在这里插入图片描述

这里还能指定工厂

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

这样的不能够自定义,所以我们如果想要自定义线程池就使用ThreadPoolExecutor的全参构造就可以了。

执行线程

Executors创建的采用sumbit执行
在这里插入图片描述
ThreadPoolExecutor而是使用execute方法
在这里插入图片描述

最佳线程池大小

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目 * cpu利用率

比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到: ((0.5+1.5)/0.5)*8=32。

时间可以使用thread dump等压测工具

volatile

用于修饰共享变量,一旦一个共享变量(类成员变量,静态变量)被volatile修饰后。就被线程所共享,且不会被指令重排序。

jit优化

如果被修饰了,那么当其被线程调用的时候不会被jit优化。
如下面情况即使被修改了,代码也没有停止,没有跳出线程2的循环。
在这里插入图片描述

被修饰后,直接就跳出了。
在这里插入图片描述

指令重排序

指令是可能被优化重新排序的如下面情况。

a = 10;
b = 1;
a = 100;

那么其很有可能被优化为

a = 10;
a = 100;
b = 1;

因为第一种,需要从内存存储a移动到b在回到a。而第二种只有一次。很显然第二种更高。

但是有时候我们是不需要其执行重排序的。
如我们想要通过a的值判断代码执行到哪里了,那么我们肯定就不希望其进行重排序。

volatile关键字,

  • 会在写操作的时候阻止上方的指令到其下方。
    在写操作时,volatile关键字会阻止上方的指令重排序到其下方。这是为了确保写操作对其他线程的读操作的可见性。如果写操作的指令重排序到其下方,其他线程可能会读取到旧的值,导致可见性问题。
  • 读操作阻止下方的到上方。
    在读操作时,volatile关键字会阻止下方的指令重排序到其上方。这是为了确保读操作读取的是最新的值。如果读操作的指令重排序到其上方,读取到的值可能是旧的值,导致可见性问题。

所以一般volatile变量

  • 如果是写一般放最后
  • 如果是读一般放前面

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

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

相关文章

人脸检索 M:N(视频,摄像头),调用百度API

目录 创建百度智能云账号 代码部分&#xff1a; 创建百度智能云账号 网址&#xff1a; 百度智能云-云智一体深入产业 点击导航栏中的产品&#xff0c;再选择人脸与人体 再选择人脸搜索 进入后&#xff0c;可以先去领取免费资源&#xff0c;如果不领取&#xff0c;后面是无法…

macOS Sourcetree 选择文件比较工具 Kaleidoscope

Sourcetree 选择文件比较工具 Kaleidoscope Kaleidoscope 使用的命令行工具是 ksdiff。Sourcetree 集成 Kaleidoscope之前&#xff0c;必须先安装 ksdiff。 Kaleidoscope 使用的命令行工具是 ksdiff。Sourcetree 集成 Kaleidoscope之前&#xff0c;必须先安装 ksdiff。 打开Ka…

5.3 用户定义的完整性

思维导图&#xff1a; 5.3 用户定义的完整性 用户定义的完整性是确保数据库中的数据满足特定应用的语义要求。这是通过关系数据库管理系统(RDBMS)中的内置机制来完成的&#xff0c;不需要依赖应用程序来执行。 5.3.1 属性上的约束条件 1. 定义属性上的约束条件 当在CREATE T…

架构风格-架构师(六十六)

管道-过滤器和仓库 数据处理方式&#xff1a; 管道过滤器是 数据驱动机制&#xff0c;处理流程事前确定&#xff0c;交互差。 仓库是通过仓库中间件交互&#xff0c;交互性强&#xff0c;灵活组装 系统可扩展性&#xff1a; 管道过滤器是数和处理在一起&#xff0c;需要新增…

金Gien乐道 | 10月热点回顾

收获之秋&#xff0c;中电金信Q4开篇捷报不断 Q4开篇&#xff0c;中电金信迎来多个捷报。公司与青岛财通集团联合打造的核心业务系统&#xff08;一体化业务平台&#xff09;一期项目顺利投产上线并平稳运行&#xff1b;中标华南某全国性股份制商业银行新一代云原生分布式核心系…

系列四、全局配置文件mybatis-config.xml

一、全局配置文件中的属性 mybatis全局配置中的文件非常多&#xff0c;主要有如下几个&#xff1a; properties&#xff08;属性&#xff09;settings&#xff08;全局配置参数&#xff09;typeAliases&#xff08;类型别名&#xff09;typeHandlers&#xff08;类型处理器&am…

prompt工程

微信公众号转载&#xff0c;关注微信公众号掌握更多技术动态 --------------------------------------------------------------- 一、prompt基础 提示包括传递给语言模型的指令和语境&#xff0c;以实现预期的任务。提示工程是开发和优化提示的实践&#xff0c;以便在各种应用…

YOLO目标检测——安全帽佩戴检测数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;安全帽佩戴检测数据集可以用于实时检测工作人员是否按照要求佩戴了安全帽&#xff0c;以保障他们的安全数据集说明&#xff1a;安全帽佩戴检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富&#xff0c;图片分为带头盔和没带头…

贪吃蛇代码实现与剖析(C语言)

贪吃蛇代码实现与剖析[C语言] 1.温馨提示2.最终实现版本的样子1.游戏开始-欢迎界面2.游戏运行界面3.游戏结束界面4.选择是否继续玩1.选择继续 2.选择退出游戏 3.完整代码一.Win32相关API的介绍1.控制台程序1.什么是控制台程序2.命令提示符中设置控制台窗口的大小3.控制台行和列…

【Linux】Nignx及负载均衡动静分离

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《微信小程序开发实战》。&#x1f3af;&#x1f3a…

数据标准是什么?如何建立企业的数据标准?

2023年10月25日国家数据局正式揭牌&#xff0c;由国家发展和改革委员会管理。国家数据局的主要职责是负责协调推进数据基础制度建设&#xff0c;统筹数据资源整合共享和开发利用&#xff0c;统筹推进数字中国、数字经济、数字社会规划和建设等。国家越来越重视数据资源的价值&a…

Explaining and harnessing adversarial examples

Explaining and harnessing adversarial examples----《解释和利用对抗样本》 背景&#xff1a; 早期的研究工作认为神经网络容易受到对抗样本误导是由于其非线性特征和过拟合。 创新点&#xff1a; 该论文作者认为神经网络易受对抗性扰动影响的主要原因是它的线性本质&#xf…

第五章 I/O管理 十、磁盘调度算法(FCFS、SSTF、SCAN、C-SCAN、C-LOOK)

目录 一、概括 二、一次磁盘读/写操作需要的时间 1、寻找时间&#xff08;寻道时间)&#xff1a; 2、延迟时间&#xff1a; 3、传输时间&#xff08;读磁盘的时间&#xff09; 三、先来先服务算法&#xff08;FCFS&#xff09; 1、定义&#xff1a; 2、例子&#xff1…

Qwt QwtPolarPlot类使用

1.概述 QwtPolarPlot是Qwt库中用于绘制极坐标图的类。它继承自QwtPolarItemDict和QFrame类&#xff0c;并且可以作为QwtPlot控件的一部分使用。 以下是类的继承关系图&#xff1a; 2.常用方法 设置标签&#xff1a; void setTitle (const QString &)void setTitle (con…

降低毕业论文写作压力的终极指南

亲爱的同学们&#xff0c;时光荏苒&#xff0c;转眼间你们即将踏入毕业生的行列。毕业论文作为本科和研究生阶段的重要任务&#xff0c;不仅是对所学知识的综合运用&#xff0c;更是一次对自己学术能力和专业素养的全面考验。然而&#xff0c;论文写作常常伴随着压力和焦虑&…

YOLOv5:修改backbone为MobileOne

YOLOv5&#xff1a;修改backbone为MobileOne 前言前提条件相关介绍MobileOneYOLOv5修改backbone为MobileOne修改common.py修改yolo.py修改yolov5.yaml配置 参考 前言 记录在YOLOv5修改backbone操作&#xff0c;方便自己查阅。由于本人水平有限&#xff0c;难免出现错漏&#xf…

前端基础之JavaScript

JavaScript是一种能够在网页上添加交互效果的脚本语言&#xff0c;也被称为客户端语言。它可以在网页中操作HTML元素、改变CSS样式&#xff0c;以及处理用户的交互事件等。 以下是JavaScript的常见基础知识点&#xff1a; 变量和数据类型&#xff1a;JavaScript中的变量可以存…

YOLOv7优化:独家创新(Partial_C_Detect)检测头结构创新,实现涨点 | 检测头新颖创新系列

💡💡💡本文独家改进:独家创新(Partial_C_Detect)检测头结构创新,适合科研创新度十足,强烈推荐 SC_C_Detect | 亲测在多个数据集能够实现大幅涨点 收录: YOLOv7高阶自研专栏介绍: http://t.csdnimg.cn/tYI0c ✨✨✨前沿最新计算机顶会复现 🚀🚀🚀YOLO…

【实现多个接口的使用】

文章目录 前言实现多个接口接口间的继承接口使用实例给对象数组排序创建一个比较器 总结 前言 实现多个接口 Java中不支持多继承&#xff0c;但是一个类可以实现多个接口 下面是自己反复理了很久才敲出来的&#xff0c;涉及到之前学的很多知识点 如果哪看不懂&#xff0c;真…

基于向量数据库的文档检索实战

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 在过去的六个月里&#xff0c;我一直在 A 系列初创公司 Voxel51 工作&#xff0c;该公司是开源计算机视觉工具包 FiftyOne 的创建者。 作为一名机器学习工程师和开发人员布道者&#xff0c;我的工作是倾听我们的开源社区的…