当前位置: 首页 > news >正文

java干货 线程池的分析和使用

文章目录

    • 一、了解线程池
      • 1.1 什么是线程池
      • 1.2 为什么需要线程池
    • 二、四种线程池的使用
      • 2.1 newFixedThreadPool
      • 2.2 newCachedThreadPool
      • 2.3 newSingleThreadExecutor
      • 2.4 newScheduledThreadPool
    • 三、自定义线程池
      • 3.1 线程池七大核心参数
      • 3.2 线程池内部处理逻辑

一、了解线程池

1.1 什么是线程池

线程池就是一个装有多个线程的容器,我们不需要关心线程的创建,在需要时从线程池获取线程来执行即可。线程池提前创建和维护了一定数量的线程,避免线程频繁创建和销毁带来的性能损耗,同时能提高响应速度。

1.2 为什么需要线程池

我们需要一个线程来执行任务,直接 new 一个不就好了吗?确实是这样,写个demo直接创建线程就好,没必要线程池。但是在并发环境下需要创建多个线程来执行任务,每个线程执行的时间都很短,频繁的创建和销毁线程会耗费时间,因此需要线程池

二、四种线程池的使用

2.1 newFixedThreadPool

创建固定线程数量的线程池

  • newFixedThreadPool(int nThreads) 源码
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
  • 实际调用 ThreadPoolExecutor,七大参数
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}
  • 特点

    • 每个线程都是核心线程
    • 使用默认的线程工厂
    • 使用默认的拒绝策略
  • 使用

class TaskRunnable implements Runnable{private static int ticketCount = 5;@Overridepublic synchronized void run() {if(ticketCount > 0){System.out.println(Thread.currentThread().getName() + " 售出第" + ticketCount + "张票");ticketCount--;}else {System.out.println(Thread.currentThread().getName()  + "没票了");}}
}
public class Demo12 {public static void main(String[] args) {TaskRunnable taskRunnable = new TaskRunnable();ExecutorService executorService = Executors.newFixedThreadPool(3);for (int i = 0; i < 8; i++) {executorService.submit(taskRunnable);}}
}
pool-1-thread-2 售出第5张票
pool-1-thread-1 售出第4张票
pool-1-thread-3 售出第3张票
pool-1-thread-3 售出第2张票
pool-1-thread-3 售出第1张票
pool-1-thread-1没票了
pool-1-thread-2没票了
pool-1-thread-3没票了

分析:提交八个任务,由三个线程完成。

2.2 newCachedThreadPool

只要有任务需要处理,线程池可以无限制地创建新线程,如果有空闲的线程可以复用,则不会创建新线程。

  • newCachedThreadPool() 源码
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}
  • 实际调用 ThreadPoolExecutor 七大参数
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}
  • 特点

    • 无核心线程
    • 根据需要动态扩缩容
    • 默认情况下,空闲线程的存活时间为 60 秒。如果线程在 60 秒内没有被使用,将被终止并从缓存中移除
  • 使用

class TaskRunnable implements Runnable{private static int ticketCount = 5;@Overridepublic synchronized void run() {if(ticketCount > 0){System.out.println(Thread.currentThread().getName() + " 售出第" + ticketCount + "张票");ticketCount--;}else {System.out.println(Thread.currentThread().getName()  + "没票了");}}
}
public class Demo12 {public static void main(String[] args) throws InterruptedException {TaskRunnable taskRunnable = new TaskRunnable();ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {executorService.submit(taskRunnable);}Thread.sleep(2000);System.out.println("-----------------------------继续提交-----------------------------------");for (int i = 0; i < 15; i++) {executorService.submit(taskRunnable);}}
}
pool-1-thread-3 售出第5张票
pool-1-thread-10 售出第4张票
pool-1-thread-9 售出第3张票
pool-1-thread-8 售出第2张票
pool-1-thread-7 售出第1张票
pool-1-thread-6没票了
pool-1-thread-2没票了
pool-1-thread-4没票了
pool-1-thread-5没票了
pool-1-thread-1没票了
-----------------------------继续提交-----------------------------------
pool-1-thread-7没票了
pool-1-thread-1没票了
pool-1-thread-6没票了
pool-1-thread-7没票了
pool-1-thread-9没票了
pool-1-thread-3没票了
pool-1-thread-8没票了
pool-1-thread-2没票了
pool-1-thread-12没票了
pool-1-thread-10没票了
pool-1-thread-4没票了
pool-1-thread-7没票了
pool-1-thread-11没票了
pool-1-thread-1没票了
pool-1-thread-5没票了

分析:可以发现提交十个任务,就创建了十个线程。在继续提交十五个任务时,会复用之前的十个线程,由于线程不够,继续创建了第十一和第十二个线程。

2.3 newSingleThreadExecutor

线程池中只有一个线程

  • newSingleThreadExecutor() 源码
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}
  • 实际调用 ThreadPoolExecutor 七大参数
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}
  • 特点

    • 只有一个线程,该线程也是核心线程
    • 适用于同步场景
  • 使用

class TaskRunnable implements Runnable{private static int ticketCount = 5;@Overridepublic synchronized void run() {if(ticketCount > 0){System.out.println(Thread.currentThread().getName() + " 售出第" + ticketCount + "张票");ticketCount--;}else {System.out.println(Thread.currentThread().getName()  + "没票了");}}
}
public class Demo12 {public static void main(String[] args) throws InterruptedException {TaskRunnable taskRunnable = new TaskRunnable();ExecutorService executorService = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {executorService.submit(taskRunnable);}}
}
pool-1-thread-1 售出第5张票
pool-1-thread-1 售出第4张票
pool-1-thread-1 售出第3张票
pool-1-thread-1 售出第2张票
pool-1-thread-1 售出第1张票
pool-1-thread-1没票了
pool-1-thread-1没票了
pool-1-thread-1没票了
pool-1-thread-1没票了
pool-1-thread-1没票了

分析:可以看出只有一个线程在执行任务,是串行

2.4 newScheduledThreadPool

延迟执行、定时执行

  • newScheduledThreadPool(核心线程数) 源码
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {return new ScheduledThreadPoolExecutor(corePoolSize);}
  • 实际调用 ScheduledThreadPoolExecutor(int corePoolSize)
public ScheduledThreadPoolExecutor(int corePoolSize) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue());}
  • 使用
class TaskRunnable implements Runnable{private static int ticketCount = 5;@Overridepublic synchronized void run() {if(ticketCount > 0){System.out.println(Thread.currentThread().getName() + " 售出第" + ticketCount + "张票");ticketCount--;}else {System.out.println(Thread.currentThread().getName()  + "没票了");}}
}
public class Demo12 {public static void main(String[] args) throws InterruptedException {TaskRunnable taskRunnable = new TaskRunnable();ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);for (int i = 0; i < 10; i++) {scheduledExecutorService.schedule(taskRunnable,3, TimeUnit.SECONDS);}}
}

分析:延迟,3 秒后执行。

  • scheduleAtFixedRate,1 秒后执行,每两秒执行一次
scheduledExecutorService.scheduleAtFixedRate(taskRunnable,1,2,TimeUnit.SECONDS);
  • 初始延迟为 0 秒。每次任务执行完成后,等待 2 秒再开始下一次执行。任务模拟执行时间为 2 秒。
scheduledExecutorService.scheduleWithFixedDelay(taskRunnable,0,2,TimeUnit.SECONDS);

-shutdown() :线程池不再接受新任务,但会继续执行已经提交的任务,直到所有任务执行完毕。

  • awaitTermination(2,TimeUnit.SECONDS) 判断2 秒内是否能完成全部任务

三、自定义线程池

3.1 线程池七大核心参数

  • 源码
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}

参数说明

  • 1.corePoolSize :核心线程数
  • 2.maximumPoolSize:最大线程数(核心线程数 + 核心线程数)
  • 3.keepAliveTime :非核心线程空闲时间,没有任务处理空闲超过该时间,线程会处于终止状态
  • 4.TimeUnit : 空闲时间单位
  • 5.BlockingQueue workQueue :任务队列
  • 6.ThreadFactory :线程工厂
  • 7.RejectedExecutionHandler :拒绝策略

3.2 线程池内部处理逻辑

三问:

  • 先问核心线程还够不够用
  • 再问任务队列是否已满
  • 最后问是否已达到最大线程数
  • 如果任务队列已满,那么创建非核心线程
  • 如果任务队列已满,同时达到最大线程数,再添加任务,则执行拒绝策略。

创建和使用自定义线程池

class TaskRunnable implements Runnable{private static int ticketCount = 5;@Overridepublic synchronized void run() {if(ticketCount > 0){System.out.println(Thread.currentThread().getName() + " 售出第" + ticketCount + "张票");ticketCount--;}else {System.out.println(Thread.currentThread().getName()  + "没票了");}try {Thread.sleep(1000); // 模拟做其他事情} catch (InterruptedException e) {e.printStackTrace();}}
}
public class Demo12 {public static void main(String[] args) throws InterruptedException {// 核心线程数int corePoolSize = 2;// 最大线程数int maximumPoolSize = 4;// 线程空闲时间long keepAliveTime = 10;// 时间单位TimeUnit unit = TimeUnit.SECONDS;// 工作队列BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(2);// 线程工厂ThreadFactory threadFactory = Executors.defaultThreadFactory();// 拒绝策略RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();// 创建自定义线程池ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);TaskRunnable taskRunnable = new TaskRunnable();for (int i = 0;i < 6; i++){executor.execute(taskRunnable);}}
}
  • 当提交的任务数 <= (最大线程数 + 任务队列大小),能正常工作
pool-1-thread-1 售出第5张票
pool-1-thread-4 售出第4张票
pool-1-thread-3 售出第3张票
pool-1-thread-2 售出第2张票
pool-1-thread-4 售出第1张票
pool-1-thread-1没票了
  • 当提交的任务数 > (最大线程数 + 任务队列大小),触发拒绝策略
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.binbin.usethread.TaskRunnable@34a245ab rejected from java.util.concurrent.ThreadPoolExecutor@7cc355be[Running, pool size = 4, active threads = 4, queued tasks = 2, completed tasks = 0]at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2065)at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:833)at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1365)at com.binbin.usethread.Demo12.main(Demo12.java:50)
pool-1-thread-1 售出第5张票
pool-1-thread-4 售出第4张票
pool-1-thread-3 售出第3张票
pool-1-thread-2 售出第2张票
pool-1-thread-4 售出第1张票
pool-1-thread-1没票了

相关文章:

java干货 线程池的分析和使用

文章目录 一、了解线程池1.1 什么是线程池1.2 为什么需要线程池 二、四种线程池的使用2.1 newFixedThreadPool2.2 newCachedThreadPool2.3 newSingleThreadExecutor2.4 newScheduledThreadPool 三、自定义线程池3.1 线程池七大核心参数3.2 线程池内部处理逻辑 一、了解线程池 …...

文本张量入门

张量&#xff0c;英文为Tensor&#xff0c;是机器学习的基本构建模块&#xff0c;是以数字方式表示数据的形式。 张量的基本类型: 创建一个标量&#xff08;0维张量&#xff09;&#xff0c;也就是一个单独的数字 scalar torch.tensor(7) scalar.ndim # 返回张量的维度 0 # …...

js文字如何轮播?

<div class"td-style"> <span class"td-text">内容1内容1内容1内容1内容1内容1</span> </div> css&#xff1a; <style> .td-style { width: 160px; height: 72px; overflow: hidden; white-…...

Linux 五种IO模型

注&#xff1a;还有一种信号驱动IO&#xff0c;使用较少暂不讨论&#xff1b; 一&#xff0c;区分阻塞、非阻塞和同步、异步 看了很多文章对这两组概念解释和对比&#xff0c;说的太复杂了&#xff0c;其实没必要&#xff0c;两句话就能说清楚。 首先&#xff0c;对于读数据rec…...

深度解析响应式异步编程模型

上一篇文章中我们聊了一下线程池,基于线程池的多线程编程是我们在高并发场景下提升系统处理效率的有效手段,但却不是唯一的。今天我们来看一下另一种异步开发的常用手段-响应式编程模型 传统多线程模型的缺陷 多线程模型是目前应用最为广泛的并发编程手段,但凡遇到什么性能…...

一个软件是如何开发出来的呢?

一、前言 如今&#xff0c;AI大爆发的时代&#xff0c;作为一名IT从业者&#xff0c;你是否也想尝试开发一套自己的系统&#xff0c;实现那些看似有可能实现的天马行空的想法&#xff0c;变成一个优秀甚至伟大的产品&#xff0c;甚至带来某个行业的革新&#xff0c;那作为一名…...

宝塔板面有哪些优势

哈喽呀&#xff0c;大家好呀&#xff0c;淼淼又来和大家见面啦&#xff0c;在当今数字化时代&#xff0c;随着云计算和互联网技术的飞速发展&#xff0c;服务器管理成为了许多企业和个人开发者不可或缺的一部分。然而&#xff0c;传统服务器管理方式的复杂性和技术门槛往往令初…...

Mybatis中BaseEntity作用

新建各种对象的时候&#xff0c;一般来说&#xff0c;有几个属性是所有对象共有的&#xff0c;比如说id,is_del&#xff0c;is_enable这些&#xff0c;然后设置一个基础对象&#xff0c;以后新建所有对象的时候都继承它&#xff0c;就省的每次都要写这些共有的属性了...

IDEA2023中使用run Dashboard面板?实现批量运行微服务

1、直接点击Add service--->Run Configuration Type---->Spring Boot 2、这样就出现了run Dashboard面板&#xff0c;可同时运行多个工程模块&#xff0c;shift选中所有启动类组命名&#xff08;Group Configurations&#xff09; 3、启动所有的项目...

分数受限,鱼和熊掌如何兼得?专业or学校,这样选最明智!

文章目录 引言一、专业解析二、名校效应分析三、好专业和好学校的权衡结论个人建议 引言 24年高考帷幕落下&#xff0c;一场新的思考与选择悄然来临。对于每一位高考考生&#xff0c;学校和专业都是开启大学新生活的两个前置必选项。但有时候“鱼与熊掌不可兼得”&#xff0c;…...

CentOS 8.5 - 配置ssh的免密登录

文章目录 生成ssh密钥公钥内容放入服务器 生成ssh密钥 在本地主机安装 ssh工具&#xff0c;并生成公钥、私钥。 # 命令行输入 ssh-keygen -r rsa# 会在当前用户的家目录下生成一个.ssh目录公钥内容放入服务器 将上一步生成的id_rsa.pub公钥的内容复制到远程服务器 # 编辑文…...

反转链表(java精简版)

反转一个单向链表。 public class ReversingLinkedList {static class Node {int val;Node next;public Node(int val) {this.val val;}public boolean hasNext() {return next ! null;}}public static void main(String[] args) {//构造Node head null;Node shift null;for…...

QPair使用详解

QPair使用详解 一、创建和初始化 QPair1.1 QPair默认构造1.2 使用值初始化1.3 QPair拷贝构造 二、访问 QPair 的值2.1 修改 QPair 的值2.2 比较 QPair2.3 使用 qMakePair 辅助函数2.4 使用 QPair 的场景 三、QPair自定结构体3.1 定义自定义结构体3.2 在 QPair 中使用自定义结构…...

C# 语言在AGI 赛道上能做什么

自从2022年11月OpenAI正式对外发布ChatGPT依赖&#xff0c;AGI 这条赛道上就挤满了重量级的选手&#xff0c;各大头部公司纷纷下场布局。原本就在机器学习、深度学习领域占据No.1的Python语言更是继续稳固了自己AI一哥的位置。凭借着Microsoft 和 OpenAI 长期以来一直是紧密相连…...

微信小程序-API 本地存储

一.本地存储-同步API 存储 &#xff1a; wx.setStorageSync 获取&#xff1a;wx.getStorageSync 删除&#xff1a;wx.removeStorageSync 清空&#xff1a;wx.clearStorageSync 代码&#xff1a; save(){//存储wx.setStorageSync(id, 1) wx.setStorageSync(obj, {name:"te…...

TensorFlow音频分类修复

原先传wav格式,后来发现前端生成的wav格式不完整 后端改mp3 其实是mp3和wav都可以接收 前端MP3和wav格式不正确,导致可以接收,但都无法计算时长 该文作废,可能导致音频分类不准确 修复TensorFlow放到生产后报错问题-CSDN博客 依赖 <dependency><groupId>or…...

C#学习系列之ListView垂直滚动

C#学习系列之ListView垂直滚动 前言垂直滚动总结 前言 当ListView中不断增加新内容&#xff0c;经常是纵向滚动。 垂直滚动 这个是关键&#xff1a;<VirtualizingStackPanel/> <ListView.ItemsPanel><ItemsPanelTemplate><VirtualizingStackPanel/>&…...

MySQL 常用函数总结

MySQL 提供了丰富的内置函数&#xff0c;用于在查询中进行各种计算、字符串处理、日期和时间操作等。这些函数可以帮助我们更有效地从数据库中检索和处理数据。下面将总结一些 MySQL 中常用的函数及其用法。 1. 数值函数 1.1 ROUND() ROUND() 函数用于对数值进行四舍五入操作…...

SpingBoot快速入门下

响应HttpServietResponse 介绍 将ResponseBody 加到Controller方法/类上 作用&#xff1a;将方法返回值直接响应&#xff0c;如果返回值是 实体对象/集合&#xff0c;将会自动转JSON格式响应 RestController Controller ResponseBody; 一般响应 统一响应 在实际开发中一般…...

什么是symbol?

在ES6&#xff08;ECMAScript 2015&#xff09;中&#xff0c;Symbol是一种新的基本数据类型&#xff0c;它的主要特点是独一无二且不可变。以下是关于ES6中Symbol的详细解释&#xff1a; 定义与特性&#xff1a; Symbol是ES6引入的一种基本数据类型&#xff0c;用于表示独一无…...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战

“&#x1f916;手搓TuyaAI语音指令 &#x1f60d;秒变表情包大师&#xff0c;让萌系Otto机器人&#x1f525;玩出智能新花样&#xff01;开整&#xff01;” &#x1f916; Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制&#xff08;TuyaAI…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

Go语言多线程问题

打印零与奇偶数&#xff08;leetcode 1116&#xff09; 方法1&#xff1a;使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...

免费数学几何作图web平台

光锐软件免费数学工具&#xff0c;maths,数学制图&#xff0c;数学作图&#xff0c;几何作图&#xff0c;几何&#xff0c;AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...