Java避坑案例 - 线程池使用中的风险识别与应对

文章目录

  • 线程池的基本概念
  • 创建线程池的注意事项
    • 实例1: `newFixedThreadPool` 使用无界队列,可能因任务积压导致 OOM
    • 实例2: `newCachedThreadPool` 会创建大量线程,可能因线程数量过多导致无法创建新线程。
  • 线程池参数设置的最佳实践
    • 线程池默认的工作行为
    • 预先启动核心线程
  • 监控线程池状态的方法
  • 线程池的混用策略

在这里插入图片描述


  1. 线程池的基本概念和使用场景
  2. 线程池创建时的注意事项,包括手动创建与使用 Executors 类的区别
  3. 案例分析 newFixedThreadPoolnewCachedThreadPool 可能引发的问题
  4. 线程池参数设置的最佳实践
  5. 监控线程池状态
  6. 线程池的混用策略

线程池的基本概念

在程序中,我们会用各种池化技术来缓存创建昂贵的对象,比如线程池、连接池、内存池。一般是预先创建一些对象放入池中,使用的时候直接取出使用,用完归还以便复用,还会通过一定的策略调整池中缓存对象的数量,实现池的动态伸缩

线程池是一种管理线程的机制,通过重用线程来减少创建和销毁线程的开销,适用于处理短平快的任务。线程池的核心组成部分包括核心线程数、最大线程数、工作队列及拒绝策略。


创建线程池的注意事项

在 Java 中,Executors 类提供了快速创建线程池的方法,但在生产环境中,建议手动使用 ThreadPoolExecutor 来创建线程池。这是因为 Executors 提供的某些方法可能导致内存溢出(OOM)等问题。

实例1: newFixedThreadPool 使用无界队列,可能因任务积压导致 OOM

/**
 * 触发OOM(OutOfMemoryError)的测试方法
 * 通过向固定大小的线程池中提交大量任务,每个任务在执行时生成大量的字符串并保持在内存中
 * 直到线程池被关闭,以此来模拟和测试OOM的情况
 * 
 * @throws InterruptedException 如果在等待线程池终止时被中断
 */
@GetMapping("oom1")
public void oom1() throws InterruptedException {
    // 创建一个固定大小为1的线程池,以控制并发任务的数量
    ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
    // 打印线程池的统计信息,监控线程池的状态
    printStats(threadPool);
    
    // 提交给线程池大量的任务,以模拟高并发的场景
    for (int i = 0; i < 100000000; i++) {
        // 每个任务在执行时会生成一个很大的字符串,并尝试将其保持在内存中
        threadPool.execute(() -> {
            // 生成一个由大量字符组成的字符串,以占用大量内存
            String payload = IntStream.rangeClosed(1, 1000000)
                    .mapToObj(__ -> "a")
                    .collect(Collectors.joining("")) + UUID.randomUUID().toString();
            // 使当前任务暂停1小时,模拟长时间运行的任务
            try {
                TimeUnit.HOURS.sleep(1);
            } catch (InterruptedException e) {
                // 捕获中断异常,但不执行任何操作
            }
            // 记录生成的字符串,进一步增加内存的使用
            log.info(payload);
        });
    }
    
    // 关闭线程池,不再接受新的任务
    threadPool.shutdown();
    // 等待线程池中的所有任务完成,或直到指定的超时时间结束
    threadPool.awaitTermination(1, TimeUnit.HOURS);
}




/**
 * 定期打印线程池的运行统计信息
 * 此方法内部创建了一个新的单线程调度器,用于定期执行打印线程池统计信息的任务
 * 它提供了线程池大小、活动线程数、已完成任务数和队列中任务数的信息
 * 这些信息有助于监控线程池的性能和工作负载
 * 
 * @param threadPool 线程池对象,其统计信息将被打印
 */
private void printStats(ThreadPoolExecutor threadPool) {
    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
        // 打印分割线,用于区分不同的统计时间点
        log.info("=========================");
        // 打印线程池当前的线程数量
        log.info("Pool Size: {}", threadPool.getPoolSize());
        // 打印当前活动线程的数量
        log.info("Active Threads: {}", threadPool.getActiveCount());
        // 打印已完成任务的总数
        log.info("Number of Tasks Completed: {}", threadPool.getCompletedTaskCount());
        // 打印队列中等待执行的任务数量
        log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());
        // 再次打印分割线,结束本次统计信息的打印
        log.info("=========================");
    }, 0, 1, TimeUnit.SECONDS);
}


在这里插入图片描述

newFixedThreadPool 方法线程池的工作队列直接 new 了一个
LinkedBlockingQueue,而默认构造方法的 LinkedBlockingQueue 是一个Integer.MAX_VALUE 长度的队列,可以认为是无界的

虽然使用 newFixedThreadPool 可以把工作线程控制在固定的数量上,但任务队列是无界的。如果任务较多并且执行较慢的话,队列可能会快速积压,撑爆内存导致 OOM


实例2: newCachedThreadPool 会创建大量线程,可能因线程数量过多导致无法创建新线程。

/**
 * 触发OOM(OutOfMemoryError)的测试方法
 * 通过创建大量的线程和字符串对象,最终导致内存溢出
 * 此方法主要用于演示和测试目的,实际应用中应避免此类设计
 */
@GetMapping("oom2")
public void oom2() throws InterruptedException {
    // 创建一个可缓存的线程池,按需(每个任务)创建新线程
    ThreadPoolExecutor threadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
    // 打印线程池的统计信息
    printStats(threadPool);
    // 循环提交大量任务到线程池
    for (int i = 0; i < 100000000; i++) {
        // 每个任务生成一个随机UUID字符串,并尝试休眠1小时
        threadPool.execute(() -> {
            String payload = UUID.randomUUID().toString();
            try {
                TimeUnit.HOURS.sleep(1);
            } catch (InterruptedException e) {
                // 捕获中断异常,但不执行任何操作
            }
            // 日志记录生成的UUID字符串
            log.info(payload);
        });
    }
    // 关闭线程池,不再接受新任务
    threadPool.shutdown();
    // 等待线程池中的所有任务完成,最多等待1小时
    threadPool.awaitTermination(1, TimeUnit.HOURS);
}

在这里插入图片描述

查看newCachedThreadPool 的源码可以看到,这种线程池的最大线程数是Integer.MAX_VALUE,可以认为是没有上限的,而其工作队列 SynchronousQueue 是一个没有存储空间的阻塞队列。这意味着,只要有请求到来,就必须找到一条工作线程来处理,如果当前没有空闲的线程就再创建一条新的。

由于我们的任务需要 1 小时才能执行完成,大量的任务进来后会创建大量的线程。我们知道线程是需要分配一定的内存空间作为线程栈的,比如 1MB,因此无限制创建线程必然会导致 OOM。


不建议使用 Executors 提供的两种快捷的线程池

  • 需要根据自己的场景、并发情况来评估线程池的几个核心参数,包括核心线程数、最大线程数、线程回收策略、工作队列的类型,以及拒绝策略,确保线程池的工作行为符合需求,一般都需要设置有界的工作队列和可控的线程数

  • 任何时候,都应该为自定义线程池指定有意义的名称,以方便排查问题。当出现线程数量暴增、线程死锁、线程占用大量 CPU、线程执行出现异常等问题时,我们往往会抓取线程栈。此时,有意义的线程名称,就可以方便我们定位问题

除了建议手动声明线程池以外,还建议用一些监控手段来观察线程池的状态。线程池这个组件往往会表现得任劳任怨、默默无闻,除非是出现了拒绝策略,否则压力再大都不会抛出一个异常。如果我们能提前观察到线程池队列的积压,或者线程数量的快速膨胀,往往可以提早发现并解决问题


线程池参数设置的最佳实践

根据应用场景,合理设置以下参数:

  • 核心线程数:应根据任务的并发性和执行时间进行调整。
  • 最大线程数:应限制线程数量以防止资源耗尽。
  • 工作队列:应使用有界队列来防止无穷的任务积压。
  • 拒绝策略:根据应用需求选择合适的拒绝策略,例如 AbortPolicyCallerRunsPolicy
import com.google.common.util.concurrent.ThreadFactoryBuilder;


/**
 * 处理 "good" GET 请求的控制器方法
 * 该方法演示了如何在Spring MVC环境中使用线程池执行异步任务
 * 它创建了一个固定大小的线程池,并提交了多个任务去执行
 * 
 * @return 返回 AtomicInteger 的值,用于跟踪任务的完成数量
 * @throws InterruptedException 如果在等待过程中线程被中断
 */
@GetMapping("good")
public int good() throws InterruptedException {
    // 使用 AtomicInteger 来跟踪任务的唯一标识
    AtomicInteger atomicInteger = new AtomicInteger();
    
    // 创建一个 ThreadPoolExecutor,配置核心线程数、最大线程数、空闲线程存活时间、工作队列等
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            2, 5,
            5, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(10),
            new ThreadFactoryBuilder().setNameFormat("demo-threadpool-%d").build(),
            new ThreadPoolExecutor.AbortPolicy());
    
    // 打印线程池的统计信息,监控线程池状态
    printStats(threadPool);
    
    // 生成并提交 20 个任务到线程池
    IntStream.rangeClosed(1, 20).forEach(i -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 获取并递增任务ID
        int id = atomicInteger.incrementAndGet();
        
        try {
            // 提交任务到线程池执行
            threadPool.submit(() -> {
                log.info("{} started", id);
                try {
                    TimeUnit.SECONDS.sleep(10);
                } catch (InterruptedException e) {
                }
                log.info("{} finished", id);
            });
        } catch (Exception ex) {
            // 如果任务提交失败,记录错误信息并递减任务ID
            log.error("error submitting task {}", id, ex);
            atomicInteger.decrementAndGet();
        }
    });

    // 主线程休眠60秒,等待任务完成
    TimeUnit.SECONDS.sleep(60);
    
    // 返回完成的任务数量
    return atomicInteger.intValue();
}

初始化线程池:

创建一个 AtomicInteger 对象 atomicInteger,用于记录任务ID。
创建一个 ThreadPoolExecutor 对象 threadPool,配置核心线程数为2,最大线程数为5,空闲线程存活时间为5秒,任务队列容量为10,线程工厂设置名称格式,拒绝策略为AbortPolicy。

打印线程池状态:

调用 printStats 方法,每隔1秒打印线程池的当前状态。

提交任务:

  • 使用 IntStream.rangeClosed(1, 20) 生成1到20的整数流。
  • 每次迭代中,休眠1秒,获取任务ID,尝试提交任务到线程池。
  • 任务内容为记录开始日志,休眠10秒,记录结束日志。
  • 如果提交失败,记录错误并减少任务计数。

等待和返回结果:

等待60秒后,返回提交任务的总数。

线程池默认的工作行为

  1. 核心线程数 (corePoolSize):

    • 线程池在没有任务时,会保持corePoolSize个线程活跃。
    • 当有任务提交时,线程池会首先尝试复用这些核心线程。
  2. 任务堆积

    • 如果所有核心线程都在忙碌,后续的任务会被添加到工作队列(如LinkedBlockingQueue)中等待处理。
    • 这个工作队列的大小是有限的,默认情况下是无界的。
  3. 扩容

    • 一旦工作队列满了,线程池会尝试扩容,创建新的工作线程,直到达到maximumPoolSize
    • maximumPoolSize是线程池允许的最大线程数。
  4. 拒绝策略

    • 如果队列和线程池都已满,线程池会根据预设的拒绝策略(如AbortPolicyCallerRunsPolicy等)处理新提交的任务。
  5. 线程回收

    • 当线程数大于corePoolSize时,如果线程在keepAliveTime内没有处理新任务,它们会被终止,回收至核心线程数。

以下是ThreadPoolExecutor构造函数的参数及其影响:

  • corePoolSize:核心线程数,保持活跃的最小线程数。
  • maximumPoolSize:最大线程数,能够创建的最大线程数量。
  • keepAliveTime:非核心线程闲置时间,超过此时间后将被回收。
  • unit:时间单位,与keepAliveTime一起使用。
  • workQueue:用于存储等待执行任务的队列。
  • handler:拒绝策略,用于处理无法被执行的任务。

预先启动核心线程

在Java的ThreadPoolExecutor中,预先启动核心线程意味着在创建线程池时,线程池会立即启动并激活corePoolSize个核心线程。这可以减少任务到达时的响应延迟,尤其是在预计会有大量任务同时提交的场景下。

相关参数和方法

  • 核心线程数 (corePoolSize):指定要保持活跃的最小线程数。
  • allowCoreThreadTimeOut:如果设置为true,当核心线程在keepAliveTime内没有执行任务时,它们会被回收。

预先启动核心线程的方法

可以使用prestartAllCoreThreads()方法来预先启动所有核心线程:

  • prestartCoreThread():启动单个核心线程(如果核心线程未被创建)。
  • prestartAllCoreThreads():启动所有核心线程。

演示如何预先启动核心线程:

import java.util.concurrent.*;

public class PrestartCoreThreadsExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2, // corePoolSize
            4, // maximumPoolSize
            60, // keepAliveTime
            TimeUnit.SECONDS, // unit
            new LinkedBlockingQueue<>() // workQueue
        );

        // 预先启动所有核心线程
        executor.prestartAllCoreThreads();

        // 提交任务
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println("Executing task " + taskId);
                try {
                    Thread.sleep(1000); // 模拟任务执行
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

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

监控线程池状态的方法

建议在生产环境中添加监控,定期输出线程池的状态信息。可以使用定时任务定期打印线程池的基本信息,如线程数、活跃线程数、完成任务数量等。

/**
 * 定期打印线程池的运行统计信息
 * 此方法内部创建了一个新的单线程调度器,用于定期执行打印线程池统计信息的任务
 * 它提供了线程池大小、活动线程数、已完成任务数和队列中任务数的信息
 * 这些信息有助于监控线程池的性能和工作负载
 * 
 * @param threadPool 线程池对象,其统计信息将被打印
 */
private void printStats(ThreadPoolExecutor threadPool) {
    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
        // 打印分割线,用于区分不同的统计时间点
        log.info("=========================");
        // 打印线程池当前的线程数量
        log.info("Pool Size: {}", threadPool.getPoolSize());
        // 打印当前活动的线程数量
        log.info("Active Threads: {}", threadPool.getActiveCount());
        // 打印已完成任务的总数
        log.info("Number of Tasks Completed: {}", threadPool.getCompletedTaskCount());
        // 打印队列中等待执行的任务数量
        log.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());
        // 再次打印分割线,结束本次统计信息的打印
        log.info("=========================");
    }, 0, 1, TimeUnit.SECONDS);
}


线程池的混用策略

要根据任务的“轻重缓急”来指定线程池的核心参数,包括线程数、回收策略和任务队列

  1. IO密集型任务

    • 特点:执行时间较长、数量较少,通常涉及网络请求、文件操作等。
    • 配置建议:
      • 可以增加核心线程数,因为这些任务通常会在等待IO时处于阻塞状态。
      • 不需要太大的任务队列,以避免内存消耗过大。
  2. 计算密集型任务

    • 特点:执行时间短、数量较多,主要涉及CPU计算。
    • 配置建议:
      • 线程数量应接近CPU核心数或核心数的两倍,以优化CPU资源利用。
      • 需要较长的任务队列来处理任务高峰,防止因线程不足导致任务拒绝。

优化策略

  1. 根据任务特性选择线程池

    • 对于IO密集型任务,使用较大的线程池以处理多任务并发。
    • 对于计算密集型任务,使用较小的线程池,避免线程切换的开销。
  2. 合理设置核心参数

    • corePoolSize:根据任务特性选择适当的线程数。
    • maximumPoolSize:根据系统资源设置合理的最大线程数。
    • keepAliveTime:调节非核心线程的存活时间,以便更好地应对任务波动。
  3. 使用不同类型的线程池

    • 对于短期任务,可以考虑使用CachedThreadPool,它会动态创建和回收线程。
    • 对于长期任务,可以使用FixedThreadPool,确保线程数不变,适合处理稳定负载的任务。

CachedThreadPoolFixedThreadPool的特性

  1. CachedThreadPool

    • 特点:可以动态创建线程,根据需要创建新的线程,空闲的线程会被回收,适合短时间内大量并发任务。
    • 优点:灵活性高,能够适应突发的任务需求。
    • 缺点:在任务量大且长时间运行时,可能导致资源耗尽或系统负载过高。
  2. FixedThreadPool

    • 特点:线程池中线程数固定,适合长期稳定的负载。
    • 优点:可控性强,避免了因动态创建线程导致的资源问题。
    • 缺点:在任务量大时可能会出现任务被阻塞的情况,因为线程数不够。

适用场景与局限性

  • 短期任务

    • 适合使用CachedThreadPool。当任务数量不确定且突发性强时,CachedThreadPool能够快速响应并执行任务。
    • 注意事项:应监控系统资源,以防因过多线程创建导致内存或CPU负担过重。
  • 长期任务

    • 适合使用FixedThreadPool。对于稳定负载的情况,FixedThreadPool可以确保线程数不变,提升执行效率。
    • 注意事项:线程数过少可能会导致任务排队,线程数过多可能会增加上下文切换的开销。

在这里插入图片描述

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

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

相关文章

Pytest-Bdd-Playwright 系列教程(5):仅执行测试用例的收集阶段

Pytest-Bdd-Playwright 系列教程&#xff08;5&#xff09;&#xff1a;仅执行测试用例的收集阶段 一、为什么需要仅收集测试用例二、应用场景三、方法详解【方法1】&#xff1a;添加pytest.ini文件的addopts配置项【方法2】&#xff1a;通过命令行参数运行 四、CI/CD 环境下的…

机器人技术基础(4章逆运动解算和雅克比矩阵)

逆运动解算&#xff1a; 雅克比矩阵&#xff1a; 将动力学分析转向运动的物体 下图中的 n o y 反映了机器人的姿态矩阵&#xff0c; 最后一列 p 反应了机器人在空间中的位置&#xff1a;

未来已来:人工智能赋能软件开发新篇章

引言 在数字化转型的浪潮中&#xff0c;数据已成为推动企业创新与增长的核心资产&#xff0c;而人工智能&#xff08;AI&#xff09;则是将这些数据转化为商业价值的关键动力。随着技术的迅速演进&#xff0c;AI 正逐步渗透到软件开发的各个环节&#xff0c;从需求预测到用户体…

C#实现视频会议录制(支持Windows、Linux、银河麒麟、统信UOS)

随着远程办公与异地协作越来越频繁&#xff0c;视频会议系统的使用也是越来越普遍。同时&#xff0c;用户对视频会议系统的功能也提出了更高的要求&#xff0c;比如&#xff0c;其中之一就是希望可以将整个视频会议的过程录制下来&#xff0c;以备之后可以查阅观看。 我们可以…

树莓派开发相关知识四 传感器-温湿度传感器

1、概述 使用DHT11温湿度传感器&#xff0c;传感周期为1s。 DHT11模块一般由3/4个引脚组成&#xff0c;每一次收集数据为40bit。 分别为&#xff1a; 高位在前、8bit湿度整数数据8bit湿度小数数据8bi温度整数数据8bit温度小数数据8bit校验和 我们需要解决的问题&#xff0c;…

vue3+less使用主题定制(多主题定制)可切换主题

假如要使用两套主题&#xff1a;蓝色、红色 例如&#xff1a; 首先确保自己的vue3项目有less&#xff0c;这边不多做接入解释 1、在src目录下建一个styles文件夹&#xff0c;在syles文件夹下面新建两个less文件&#xff1a;theme.less和variables.less&#xff1b; theme.le…

Spring Cloud Sleuth(Micrometer Tracing +Zipkin)

分布式链路追踪 分布式链路追踪技术要解决的问题&#xff0c;分布式链路追踪&#xff08;Distributed Tracing&#xff09;&#xff0c;就是将一次分布式请求还原成调用链路&#xff0c;进行日志记录&#xff0c;性能监控并将一次分布式请求的调用情况集中展示。比如各个服务节…

春季测试 2023 我的题解

T1 涂色游戏 这道题目还是比较简单的 容易发现&#xff0c;位于 ( x i , y i ) (x_i,y_i) (xi​,yi​) 的格子的颜色只取决于 ​ x i x_i xi​ 行与 y i y_i yi​ 列的颜色。 这时候可以想到开两个数组&#xff0c;分别存储列与行的绘画信息&#xff0c;然后发现前后的互相…

Kali Linux 新工具推荐: Sploitscan

在 2024.2 版本 Kali Linux 增加了一个新攻击工具: Sploitscan 1.简介: Sploitscan 能够发现操作系统和应用程序中的安全漏洞。 2.特点: 简单的命令行界面 扫描多个操作系统和应用程序 检测多种漏洞 提供详细信息 可定制性强 3.示例: 2024.2 及以后的版本 Kali Linux…

【JAVA 笔记】09 ch06_arrays_sort_and_search

第6章 数组、排序和查找 数组介绍 数组的使用 使用方式1-动态初始化数组的定义 使用方式2-动态初始化 使用方式3-静态初始化 数组使用注意事项和细节 数组应用案例 数组赋值机制 数组拷贝 数组添加/扩容 多维数组 二维数组 动态初始化1 动态初始化2 静态初始化 二维数组的应用案…

C语言实现归并排序

#include <stdio.h> #include <stdlib.h> #include<time.h> #include<string.h> #define N 7 // 定义元素类型为整型 typedef int ElemType; // 定义静态表结构体 typedef struct{ ElemType *elem; // 动态分配的数组指针 int TableL…

第十八章 Vue组件样式范围配置之scoped

目录 一、引言 二、案例演示 2.1. 工程结构图 2.2. 核心代码 2.2.1. main.js 2.2.2. App.vue 2.2.3. BaseOne.vue 2.2.4. BaseTwo.vue 2.3. 运行效果 2.4. 调整代码 2.4.1. BaseTwo.vue 2.4.2. 运行效果 三、scoped原理 一、引言 前面的几个章节在介绍组件的时…

Linux 中,flock 对文件加锁

在Linux中&#xff0c;flock是一个用于对文件加锁的实用程序&#xff0c;它可以帮助协调多个进程对同一个文件的访问&#xff0c;避免出现数据不一致或冲突等问题。以下是对flock的详细介绍&#xff1a; 基本原理 flock通过在文件上设置锁来控制多个进程对该文件的并发访问。…

stm32入门教程-- DMA数据转运

目录 简介 原理 实验示例 1、DMA数据转运 实现代码 实验效果 原理 实验示例 1、DMA数据转运 接线图 存储器映像 我们在开始代码之前&#xff0c;可以看下我们定义的数据&#xff0c;到底是不是真的存储在了这个相应的地址区间里&#xff0c;我们看代码&#xff1a; …

SELS-SSL/TLS

一、了解公钥加密&#xff08;非对称加密&#xff09; 非对称加密中&#xff0c;用于加密数据的密钥与用于解密数据的密钥不同。私钥仅所有者知晓&#xff0c;而公钥则可自由分发。发送方使用接收方的公钥对数据进行加密&#xff0c;数据仅能使用相应的私钥进行解密。 你可以将…

【Kettle的安装与使用】使用Kettle实现mysql和hive的数据传输(使用Kettle将mysql数据导入hive、将hive数据导入mysql)

文章目录 一、安装1、解压2、修改字符集3、启动 二、实战1、将hive数据导入mysql2、将mysql数据导入到hive 一、安装 Kettle的安装包在文章结尾 1、解压 在windows中解压到一个非中文路径下 2、修改字符集 修改 spoon.bat 文件 "-Dfile.encodingUTF-8"3、启动…

【机器学习】 15. SVM 支撑向量机 support vector machine,拉格朗日,软边界,核函数

SVM 支撑向量机 support vector machine&#xff0c;拉格朗日&#xff0c;软边界&#xff0c;核函数 1. 超平面边界 margin of hyperplane2. 边界越大的超平面越好原因 3. 线性模型通过决策边界分类4. SVM的问题5. 拉格朗日乘子与SVM结合求最大边界6. SVM软边界和硬边界7. 非线…

多线程学习篇六:park / unpark

1. API LockSupport.park()&#xff1a;暂停当前线程LockSupport.unpark (线程对象)&#xff1a;恢复某个线程的运行 1.1 先 park 再 unpark main 线程睡眠时间大于 t1 线程睡眠时间 Slf4j(topic "c.Test01") public class Test01 {public static void main(Str…

传承双百基因 大将军F9轻松跑“盈”脐橙创富路

“江作青罗带&#xff0c;山如碧玉簪。”广西河池&#xff0c;地处桂林西北一隅&#xff0c;奇秀的喀斯特地貌纵横绵延&#xff0c;亚热带季风气候温润宜人&#xff0c;堪称种植脐橙的“天选之地”。 正值秋季&#xff0c;这里漫山遍野&#xff0c;“橙”香四溢&#xff0c;大量…

一图看懂亚信安全2024年三季度财报

一图看懂亚信安全2024年三季度财报&#xff0c; 2024年前三季度营收创历史新高&#xff0c; 净利润、经营现金流加速增长&#xff01; &#xff08;相关信息主要摘自亚信安全2024年三季度财报&#xff0c; 如存在差异&#xff0c;以亚信安全2024年三季度财报为准。&#xff0…