JAVA并发编程3--多线程程序

news2025/2/12 10:23:22

1.创建线程的方法:

案例:计算1-1000的整数和

实现Runnable接口

步骤:
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start() ① 启动线程 ②调用当前线程的run()–>调用了Runnable类型的target的run()

import java.util.concurrent.atomic.AtomicLong;

public class ThreadLearn {
    // 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题
    public static AtomicLong result = new AtomicLong(0);
    static int count = 100000;
    public static CountDownLatch countDownLatch = new CountDownLatch(count);
    public static ExecutorService executor=Executors.newFixedThreadPool(10);
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        Thread[] threads = new Thread[count];
        for (int i = 0; i < count; i++) {
            MyThread myThread = new MyThread(100);
            threads[i] = new Thread(myThread);
            threads[i].start();
        }
        countDownLatch.await();
        System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值
        System.out.println(System.currentTimeMillis() - start);
    }
}
class MyThread implements Runnable {
    private int count;

    public MyThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        // 使用 AtomicLong 的 addAndGet 方法进行原子更新
        ThreadLearn.result.addAndGet(sum);
        ThreadLearn.countDownLatch.countDown();
    }
}

继承Thread类

步骤:
1.创建一个继承于Thread类的子类
2.重写Thread类的run() --> 将此线程执行的操作声明在run()中
3.创建Thread类的子类的对象
4.通过此对象调用start()执行线程

import java.util.concurrent.atomic.AtomicLong;

public class ThreadLearn {
    // 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题
    public static AtomicLong result = new AtomicLong(0);
    static int count = 100000;
    public static CountDownLatch countDownLatch = new CountDownLatch(count);
    public static ExecutorService executor=Executors.newFixedThreadPool(10);
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        Thread[] threads = new Thread[count];
        for (int i = 0; i < count; i++) {
            MyThread1 myThread = new MyThread1(100);
            threads[i] = new Thread(myThread);
            threads[i].start();
        }
        countDownLatch.await();
        System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值
        System.out.println(System.currentTimeMillis() - start);
    }
}
class MyThread1 extends Thread {
    private long count;

    MyThread1(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        // 使用 AtomicLong 的 addAndGet 方法进行原子更新
        ThreadLearn.result.addAndGet(sum);
        ThreadLearn.countDownLatch.countDown();
    }
}

实现Callable接口

步骤:
1.创建一个实现Callable的实现类
2.实现call方法,将此线程需要执行的操作声明在call()中
3.创建Callable接口实现类的对象
4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
6.获取Callable中call方法的返回值

package Reflection;

import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicLong;

public class ThreadLearn {
    // 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题
    public static AtomicLong result = new AtomicLong(0);
    static int count = 100000;
    public static CountDownLatch countDownLatch = new CountDownLatch(count);

    public static void main(String[] args) throws InterruptedException, ExecutionException {
//        long start = System.currentTimeMillis();
//        Thread[] threads = new Thread[count];
//        for (int i = 0; i < count; i++) {
//            MyThread1 myThread = new MyThread1(100);
//            threads[i] = new Thread(myThread);
//            threads[i].start();
//        }
//        countDownLatch.await();
//        System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值
//        System.out.println(System.currentTimeMillis() - start);
        FutureTask<Integer> futureTask=new FutureTask<>(new MyThread2(100));
        Thread thread=new Thread(futureTask);
        thread.start();
        Integer o = futureTask.get();
        System.out.println(o);

    }
}

class MyThread implements Runnable {
    private int count;

    public MyThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        // 使用 AtomicLong 的 addAndGet 方法进行原子更新
        ThreadLearn.result.addAndGet(sum);
        ThreadLearn.countDownLatch.countDown();
    }
}

class MyThread1 extends Thread {
    private long count;

    MyThread1(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        // 使用 AtomicLong 的 addAndGet 方法进行原子更新
        ThreadLearn.result.addAndGet(sum);
        ThreadLearn.countDownLatch.countDown();
    }
}

class MyThread2 implements Callable<Integer> {
    private int count;

    public MyThread2(int count) {
        this.count=count;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        return sum;
    }
}

使用线程池

package Reflection;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

public class ThreadLearn {
    // 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题
    public static AtomicLong result = new AtomicLong(0);
    static int count = 100000;
    public static CountDownLatch countDownLatch = new CountDownLatch(count);
    //线程池
    public static ExecutorService executor=Executors.newFixedThreadPool(10);
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Future<Integer> submit = executor.submit(new MyThread2(100));
        Integer i=submit.get();
        System.out.println(i);
    }
}


class MyThread2 implements Callable<Integer> {
    private int count;

    public MyThread2(int count) {
        this.count=count;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        return sum;
    }
}

2.统计多线程程序的运行时间

如果要统计多线程程序的执行时间,主线程等待所有子线程完成,那么总时间可能接近实际所有线程执行完毕的时间。所以可能需要确保主线程在所有子线程结束后才结束,然后计算整个程序的运行时间。

使用join方法

使用thread.join,` 让一个线程等待另一个线程执行完毕。在多线程编程中,有时我们需要确保某些线程在其他线程完成特定任务后再继续执行,这时就可以使用 join() 方法来实现线程间的同步。

package Reflection;

import java.util.concurrent.atomic.AtomicLong;

public class ThreadLearn {
    // 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题
    public static AtomicLong result = new AtomicLong(0);

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        int count = 10000; // 将线程数量和数组长度统一为 10000
        Thread[] threads = new Thread[count];
        for (int i = 0; i < count; i++) {
            MyThread myThread = new MyThread(100);
            threads[i] = new Thread(myThread);
            threads[i].start();
        }

        for (Thread thread : threads) {
            thread.join();
        }

        System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值
        System.out.println(System.currentTimeMillis() - start);
    }
}

class MyThread implements Runnable {
    private int count;

    public MyThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        // 使用 AtomicLong 的 addAndGet 方法进行原子更新
        ThreadLearn.result.addAndGet(sum);
    }
}

在这里插入图片描述

使用CountDownLatch

CountDownLatch 是 Java 并发包 java.util.concurrent 中的一个同步辅助类,主要用于协调多个线程之间的执行顺序。它可以让一个或多个线程等待其他一组线程完成它们的操作后再继续执行,从而确保程序的执行逻辑按照预期进行,避免出现数据不一致或逻辑错误的问题。

package Reflection;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;

public class ThreadLearn {
    // 使用 AtomicLong 替代 Long,保证线程安全且避免空指针问题
    public static AtomicLong result = new AtomicLong(0);
    static int count = 100000;
    public static CountDownLatch countDownLatch = new CountDownLatch(count);

    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();
        Thread[] threads = new Thread[count];
        for (int i = 0; i < count; i++) {
            MyThread myThread = new MyThread(100);
            threads[i] = new Thread(myThread);
            threads[i].start();
        }
        countDownLatch.await();
        System.out.println(result.get()); // 使用 get 方法获取 AtomicLong 的值
        System.out.println(System.currentTimeMillis() - start);
    }
}

class MyThread implements Runnable {
    private int count;

    public MyThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        // 使用 AtomicLong 的 addAndGet 方法进行原子更新
        ThreadLearn.result.addAndGet(sum);
        ThreadLearn.countDownLatch.countDown();
    }
}

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

CountDownLatch 的执行过程
1. 初始化阶段
  • 首先,需要创建一个 CountDownLatch 实例,在创建时要传入一个初始计数值。这个计数值代表了需要等待完成的操作数量。例如在代码中 CountDownLatch countDownLatch = new CountDownLatch(count);,这里的 count 就是初始计数值,它表明有 count 个任务需要完成,CountDownLatch 会将这个值存储在内部作为计数器的初始状态。
2. 任务线程启动阶段
  • 通常会有一个或多个任务线程被创建并启动去执行特定的任务。这些线程可能会并发地执行各自的任务,就像代码里通过循环创建并启动多个 Thread 实例,每个线程都去执行 MyThread 类的 run 方法中的任务。
3. 等待阶段
  • 有一个或多个线程(通常是主线程)会调用 CountDownLatchawait() 方法。当调用这个方法时,调用线程会进入阻塞状态,它会一直等待,直到 CountDownLatch 的内部计数器值变为 0。这意味着它在等待所有任务都完成。
4. 任务完成与计数器递减阶段
  • 每个任务线程在完成自己的任务后,会调用 CountDownLatchcountDown() 方法。这个方法的作用是将 CountDownLatch 的内部计数器值减 1。随着越来越多的任务线程完成任务并调用 countDown() 方法,计数器的值会不断减小。
5. 唤醒等待线程阶段
  • CountDownLatch 的内部计数器值减到 0 时,所有之前调用 await() 方法并处于阻塞状态的线程会被唤醒。这些线程会从 await() 方法处继续执行后续的代码逻辑。
6. 后续处理阶段
  • 被唤醒的线程可以继续执行后续的操作,比如代码中主线程在被唤醒后会打印最终的计算结果以及程序执行所花费的时间。
主要方法
  • CountDownLatch(int count) 构造方法:用于创建一个 CountDownLatch 实例,并初始化其内部计数器的值为 count。这个 count 表示需要等待完成的操作数量,必须是一个正整数。
  • void await() 方法:调用该方法的线程会进入阻塞状态,直到 CountDownLatch 的内部计数器值变为 0。如果在等待过程中当前线程被中断,会抛出 InterruptedException 异常。
  • boolean await(long timeout, TimeUnit unit) 方法:调用该方法的线程会等待一段时间,最多等待 timeout 时间(由 unit 指定时间单位)。如果在这段时间内计数器值变为 0,则线程会被唤醒并返回 true;如果超过指定时间计数器值仍不为 0,则线程会被唤醒并返回 false。同样,如果在等待过程中线程被中断,会抛出 InterruptedException 异常。
  • void countDown() 方法:该方法会将 CountDownLatch 的内部计数器值减 1。当计数器值减到 0 时,所有正在等待的线程会被唤醒。如果计数器值已经为 0,调用该方法不会产生任何效果。
  • long getCount() 方法:该方法用于返回 CountDownLatch 当前的计数器值。可以通过这个方法来查看还有多少个任务未完成。

3.如何解决多线程对共享变量操作的线程安全问题

package Reflection;


public class ThreadLearn {
    public static long result ;
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10000; i++) {
            MyThread myThread = new MyThread(1000);
            Thread thread = new Thread(myThread);
            thread.start();
        }
        Thread.sleep(10*1000);
        System.out.println(result);
    }

}
class MyThread implements Runnable {
    private int count;
    public MyThread(int count) {
        this.count = count;
    }
    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum+=i;
        }
        ThreadLearn.result+=sum;
        System.out.println(sum);
    }
}

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

代码功能概述

这段 Java 代码主要的功能是创建 10000 个线程,每个线程会计算从 0 到 1000 的整数之和,然后将这个和累加到 ThreadLearn 类的静态变量 result 中。最后,主线程等待 10 秒后输出 result 的值。

代码存在的问题

  1. 线程安全问题ThreadLearn.result 是一个共享变量,多个线程同时对其进行写操作(ThreadLearn.result += sum;),这会导致数据竞争(Data Race)问题,最终的 result 值可能是错误的。
  2. 等待时间不确定性:使用 Thread.sleep(10 * 1000) 来等待所有子线程完成,这种方式不够可靠,因为不同的机器性能不同,可能会导致有些线程还未执行完,主线程就已经输出结果。

你提供的代码在多线程环境下存在线程安全问题,主要是因为多个线程同时对静态变量 ThreadLearn.result 进行写操作,这可能会导致数据竞争和不一致的结果。下面为你介绍几种解决该问题的方法。

方法一:使用 synchronized 关键字

synchronized 关键字可以用来修饰方法或代码块,保证同一时刻只有一个线程能够访问被修饰的代码,从而避免多线程对共享资源的并发访问问题。

package Reflection;

public class ThreadLearn {
    public static long result;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10000; i++) {
            MyThread myThread = new MyThread(1000);
            Thread thread = new Thread(myThread);
            thread.start();
        }
        Thread.sleep(10 * 1000);
        System.out.println(result);
    }
}

class MyThread implements Runnable {
    private int count;

    public MyThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        // 使用 synchronized 块保证线程安全
        synchronized (ThreadLearn.class) {
            ThreadLearn.result += sum;
        }
        System.out.println(sum);
    }
}

解释:在 run 方法中,使用 synchronized (ThreadLearn.class)ThreadLearn.result += sum; 这行代码进行同步,这样同一时刻只有一个线程能够执行该代码块,从而保证了对 result 变量的线程安全访问。

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

方法二:使用 AtomicLong 类

AtomicLong 是 Java 提供的一个原子类,它提供了一些原子操作方法,可以保证对长整型变量的原子性更新,避免了使用 synchronized 带来的性能开销。

package Reflection;

import java.util.concurrent.atomic.AtomicLong;

public class ThreadLearn {
    // 使用 AtomicLong 代替 long
    public static AtomicLong result = new AtomicLong(0);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10000; i++) {
            MyThread myThread = new MyThread(1000);
            Thread thread = new Thread(myThread);
            thread.start();
        }
        Thread.sleep(10 * 1000);
        System.out.println(result.get());
    }
}

class MyThread implements Runnable {
    private int count;

    public MyThread(int count) {
        this.count = count;
    }

    @Override
    public void run() {
        int sum = 0;
        for (int i = 0; i <= count; i++) {
            sum += i;
        }
        // 使用 AtomicLong 的 addAndGet 方法进行原子更新
        result.addAndGet(sum);
        System.out.println(sum);
    }
}

解释:将 ThreadLearn 类中的 result 变量类型改为 AtomicLong,并使用 addAndGet 方法来更新 result 的值。addAndGet 方法是原子操作,能够保证在多线程环境下对 result 的更新是线程安全的。

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

在这里插入图片描述

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

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

相关文章

自主项目面试点总结

1、许苑–OJ判题系统 技术栈&#xff1a;Spring BootSpring Cloud AlibabaRedisMybatisMQDocker 项目地址: https://github.com/xuyuan-upward/xyoj-backend-microservice 1.1、项目介绍: 一个基于微服务的OJ系统&#xff0c;具备能够根据管理员预设的题目用例对用户提交的代…

idea Ai工具通义灵码,Copilot我的使用方法以及比较

我用过多个idea Ai 编程工具&#xff0c;大约用了1年时间&#xff0c;来体会他们那个好用&#xff0c;以下只是针对我个人的一点分享&#xff0c;不一定对你适用 仅作参考。 介于篇幅原因我觉得能说上好用的 目前只有两个 一个是阿里的通义灵码和Copilot&#xff0c;我用它来干…

4.python基础语法-下

文章目录 1.顺序语句2.条件语句 - if2.1什么是条件语句2.2语法格式2.2.1if2.2.2if - else2.2.3if - elif - else 2.3缩进和代码块2.4练习2.5空语句 pass 3.循环语句3.1while循环3.2for循环3.3continue3.4break 4.综合案例4.1设置初始属性4.2设置性别4.3设置出生点4.4针对每一岁…

Java--集合(理论)

目录 一、collection collection常用方法 1.List&#xff08;可以存在重复元素&#xff09; 迭代器 迭代器的概念 注意事项 例子 1.ArrayList 特点 2.LinkedLIst 特点 3.Vector 特点 2.Set&#xff08;无重复元素&#xff09; 1.HashSet 特点 2.Linkedhashset&…

3D图形学与可视化大屏: 3D 图形学的定义、应用领域和发展历程

一、3D 图形学的定义 3D 图形学是计算机科学的一个分支&#xff0c;主要研究如何在计算机上生成、处理和显示三维图形。它涉及到数学、物理学、计算机科学等多个学科领域&#xff0c;旨在通过计算机技术模拟真实世界中的三维物体和场景&#xff0c;为用户提供逼真的视觉体验。…

Python 面向对象(类,对象,方法,属性,魔术方法)

前言&#xff1a;在讲面向对象之前&#xff0c;我们先将面向过程和面向对象进行一个简单的分析比较&#xff0c;这样我们可以更好的理解与区分&#xff0c;然后我们在详细的讲解面向对象的优势。 面向过程&#xff08;Procedure-Oriented Programming&#xff0c;POP&#xff0…

轮子项目--消息队列的实现(3)

上一篇文章中我把一些关键的类以及表示出来&#xff0c;如何对这些类对应的对象进行管理呢&#xff1f;管理分为硬盘和内存上&#xff0c;硬盘又分为数据库&#xff08;管理交换机&#xff0c;队列和绑定&#xff09;和文件&#xff08;管理消息&#xff09;&#xff0c;本文就…

5.7.1 软件项目管理范围、成本估算、风险分析

文章目录 管理范围成本估算风险分析 管理范围 软件项目管理范围包含4P&#xff0c;即人员、产品、过程、项目。人员管理通过人员能力成熟度模型PCMM进行管理。产品管理需要制定产品目标&#xff0c;识别产品的总体目标&#xff0c;而不涉及细枝末节。产品范围&#xff0c;识别产…

Android新版高斯模糊(毛玻璃)官方实现,Kotlin

Android新版高斯模糊(毛玻璃)官方实现&#xff0c;Kotlin 从Android 12开始&#xff0c;Android官方API支持高斯模糊(毛玻璃)效果。关键是通过RenderEffect实现。 https://developer.android.com/reference/android/graphics/RenderEffecthttps://developer.android.com/refer…

仿 RabbitMQ 实现的简易消息队列

文章目录 项目介绍开放环境第三⽅库介绍ProtobufMuduo库 需求分析核⼼概念实现内容 消息队列系统整体框架服务端模块数据管理模块虚拟机数据管理模块交换路由模块消费者管理模块信道&#xff08;通信通道&#xff09;管理模块连接管理模块 客户端模块 公共模块日志类其他工具类…

实验9 基于WebGoat平台的SQL注入攻击

实验9 基于WebGoat平台的SQL注入攻击 1.实验目的 熟悉WebGoat平台&#xff0c;在该平台上实现SQL注入攻击。 2.实验内容 &#xff08;1&#xff09;下载webgoat-server-8.2.2.jar。 &#xff08;2&#xff09;搭建java环境。 &#xff08;3&#xff09;运行webgoat。 &#xf…

多光谱技术在华为手机上的应用发展历史

2018 年&#xff0c;华为 P20 系列首次搭载 5 通道色温传感器&#xff0c;可帮助手机在不同光照条件下保持画面色彩一致性。 2020 年&#xff0c;华为 P40 系列搭载 8 通道多光谱色温传感器&#xff08;实际为 11 通道&#xff0c;当时只用 8 个通道检测可见光&#xff09;&am…

如何免费白嫖 Deepseek API 接口

今天我将教大家如何利用网络空间测绘搜索引擎「Fofa」来寻找已经部署并开放 Deepseek 接口的服务。以下是详细步骤&#xff1a; 1. 访问 Fofa 搜索引擎 首先&#xff0c;打开 Fofa 搜索引擎的网站&#xff1a;https://fofa.info 2. 搜索开放的 Deepseek 接口 在搜索框中输入…

SaaS+AI应用架构:业务场景、智能体、大模型、知识库、传统工具系统

SaaSAI应用架构&#xff1a;业务场景、智能体、大模型、知识库、传统工具系统 大家好&#xff0c;我是汤师爷~ 在SaaS与AI应用的演进过程中&#xff0c;合理的架构设计至关重要。本节将详细介绍其五个核心层次&#xff1a; 业务场景层&#xff1a;发现和确定业务场景智能体层…

ios通过xib创建控件

之前写过ios动态创建控件及添加事件&#xff0c;纯手工代码写控件&#xff0c;虽然比较灵活&#xff0c;但是就是代码量比较多。这次我们通过xib来创建app下载列表项 AppView.xib。一个imageview,一个label,一个button构成 1.创建AppView.xib 2.再创建xib对应的mode&#xff0…

【树莓派Pico设备驱动】-WS2812B全彩LED驱动(基于SPI)

WS2812B全彩LED驱动(基于SPI) 文章目录 WS2812B全彩LED驱动(基于SPI)1、WS2812介绍2、WS2812配置4、驱动实现1、WS2812介绍 WS2812/WS2812B LED 使用 24 位来表示绿色、红色和蓝色值。 WS2812采用单线通信的设计,通信协议为非归零编码,每个LED需要24个bit的数据,数据依…

AIGC-微头条爆款文案创作智能体完整指令(DeepSeek,豆包,千问,Kimi,GPT)

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列AIGC(GPT、DeepSeek、豆包、千问、Kimi)👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资…

2025届优秀创新大数据毕业设计

吊打导师的大数据毕业设计项目 985华南理工大学学长 大厂全栈&#xff0c;大数据开发工程师 专注定制化开发

解决 ComfyUI-Impact-Pack 中缺少 UltralyticsDetectorProvider 节点的问题

解决 ComfyUI-Impact-Pack 中缺少 UltralyticsDetectorProvider 节点的问题 1. 安装ComfyUI-Impact-Pack 首先确保ComfyUI-Impact-Pack 已经下载 地址: https://github.com/ltdrdata/ComfyUI-Impact-Pack 2. 安装ComfyUI-Impact-Subpack 由于新版本的Impact Pack 不再提供这…

SpringBoot中的Javaconfig

为什么要使用Javaconfig&#xff1f; 如果要声明的bean对象&#xff0c;来自于第三方jar包&#xff08;不是自定义的&#xff09;&#xff0c;无法使用Component 及衍生注解来声明bean&#xff0c;因为第三方的jar一般不可写&#xff0c;需要使用注解Configuration和Bean注解来…