Java笔记:线程池

一. 正确使用ThreadPoolExecutor创建线程池

1.1、基础知识

Executors创建线程池便捷方法列表:下面三个是使用ThreadPoolExecutor的构造方法创建的

方法名功能
newFixedThreadPool(int nThreads)创建固定大小的线程池
newSingleThreadExecutor()创建只有一个线程的线程池
newCachedThreadPool()创建一个不限线程数上限的线程池,任何提交的任务都将立即执行

ThreadPoolExecutor构造方法
Executors中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor的构造方法(定时任务使用的是ScheduledThreadPoolExecutor),该类构造方法参数列表如下:

// Java线程池的完整构造函数
public ThreadPoolExecutor(int corePoolSize, // 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。int maximumPoolSize, // 线程数的上限long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的idle时长,// 超过这个时间,多余的线程会被回收。BlockingQueue<Runnable> workQueue, // 任务的排队队列ThreadFactory threadFactory, // 新线程的产生方式RejectedExecutionHandler handler) // 拒绝策略

这些参数中,比较容易引起问题的有corePoolSize, maximumPoolSize, workQueue以及handler:
*corePoolSize和maximumPoolSize设置不当会影响效率,甚至耗尽线程;
*workQueue设置不当容易导致OOM;
*handler设置不当会导致提交任务时抛出异常。

线程池的工作顺序

If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.

corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略

Runnable和Callable
可以向线程池提交的任务有两种:Runnable和Callable,二者的区别如下:
1.方法签名不同,void Runnable.run(), V Callable.call() throws Exception
2.是否允许有返回值,Callable允许有返回值
3.是否允许抛出异常,Callable允许抛出异常。

Callable是JDK1.5时加入的接口,作为Runnable的一种补充,允许有返回值,允许抛出异常。

三种提交任务的方式:

提交方式是否关心返回结果
Future submit(Callable task)
void execute(Runnable command)
Future submit(Runnable task)否,虽然返回Future,但是其get()方法总是返回null

2.2 如何正确使用线程池

避免使用无界队列
不要使用Executors.newXXXThreadPool()快捷方法创建线程池,因为这种方式会使用无界的任务队列,为避免OOM,我们应该使用ThreadPoolExecutor的构造方法

手动指定队列的最大长度:

ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512), // 使用有界队列,避免OOMnew ThreadPoolExecutor.DiscardPolicy());

明确拒绝任务时的行为
任务队列总有占满的时候,这是再submit()提交新的任务会怎么样呢?RejectedExecutionHandler接口为我们提供了控制方式,接口定义如下:

public interface RejectedExecutionHandler {void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

线程池给我们提供了几种常见的拒绝策略:

拒绝策略拒绝行为
AbortPolicy抛出RejectedExecutionException
DiscardPolicy什么也不做,直接忽略
DiscardOldestPolicy丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置
CallerRunsPolicy直接由提交任务者执行这个任务

线程池默认的拒绝行为是AbortPolicy,也就是抛出RejectedExecutionHandler异常,该异常是非受检异常,很容易忘记捕获。如果不关心任务被拒绝的事件,可以将拒绝策略设置成DiscardPolicy,这样多余的任务会悄悄的被忽略。

ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512), new ThreadPoolExecutor.DiscardPolicy());// 指定拒绝策略

获取处理结果和异常
线程池的处理结果、以及处理过程中的异常都被包装到Future中,并在调用Future.get()方法时获取,执行过程中的异常会被包装成ExecutionException,submit()方法本身不会传递结果和任务执行过程中的异常。获取执行结果的代码可以这样写:

ExecutorService executorService = Executors.newFixedThreadPool(4);
Future<Object> future = executorService.submit(new Callable<Object>() {@Overridepublic Object call() throws Exception {throw new RuntimeException("exception in call~");// 该异常会在调用Future.get()时传递给调用者}});try {Object result = future.get();
} catch (InterruptedException e) {// interrupt
} catch (ExecutionException e) {// exception in Callable.call()e.printStackTrace();
}

上述代码输出类似如下:

2.3 线程池的常用场景

正确构造线程池

int poolSize = Runtime.getRuntime().availableProcessors() * 2;
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(512);
RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy();
executorService = new ThreadPoolExecutor(poolSize, poolSize,0, TimeUnit.SECONDS,queue,policy);

获取单个结果
过submit()向线程池提交任务后会返回一个Future,调用V Future.get()方法能够阻塞等待执行结果,V get(long timeout, TimeUnit unit)方法可以指定等待的超时时间。

获取多个结果
如果向线程池提交了多个任务,要获取这些任务的执行结果,可以依次调用Future.get()获得。但对于这种场景,我们更应该使用ExecutorCompletionService,该类的take()方法总是阻塞等待某一个任务完成,然后返回该任务的Future对象。向CompletionService批量提交任务后,只需调用相同次数的CompletionService.take()方法,就能获取所有任务的执行结果,获取顺序是任意的,取决于任务的完成顺序:

void solve(Executor executor, Collection<Callable<Result>> solvers)throws InterruptedException, ExecutionException {CompletionService<Result> ecs = new ExecutorCompletionService<Result>(executor);// 构造器for (Callable<Result> s : solvers)// 提交所有任务ecs.submit(s);int n = solvers.size();for (int i = 0; i < n; ++i) {// 获取每一个完成的任务Result r = ecs.take().get();if (r != null)use(r);}
}

单个任务的超时时间
V Future.get(long timeout, TimeUnit unit)方法可以指定等待的超时时间,超时未完成会抛出TimeoutException。

多个任务的超时时间
等待多个任务完成,并设置最大等待时间,可以通过CountDownLatch完成:

public void testLatch(ExecutorService executorService, List<Runnable> tasks) throws InterruptedException{CountDownLatch latch = new CountDownLatch(tasks.size());for(Runnable r : tasks){executorService.submit(new Runnable() {@Overridepublic void run() {try{r.run();}finally {latch.countDown();// countDown}}});}latch.await(10, TimeUnit.SECONDS); // 指定超时时间}

2.4 线程池和装修公司

以运营一家装修公司做个比喻。公司在办公地点等待客户来提交装修请求;公司有固定数量的正式工以维持运转;旺季业务较多时,新来的客户请求会被排期,比如接单后告诉用户一个月后才能开始装修;当排期太多时,为避免用户等太久,公司会通过某些渠道(比如人才市场、熟人介绍等)雇佣一些临时工(注意,招聘临时工是在排期排满之后);如果临时工也忙不过来,公司将决定不再接收新的客户,直接拒单。

线程池就是程序中的“装修公司”,代劳各种脏活累活。上面的过程对应到线程池上:

// Java线程池的完整构造函数
public ThreadPoolExecutor(int corePoolSize, // 正式工数量int maximumPoolSize, // 工人数量上限,包括正式工和临时工long keepAliveTime, TimeUnit unit, // 临时工游手好闲的最长时间,超过这个时间将被解雇BlockingQueue<Runnable> workQueue, // 排期队列ThreadFactory threadFactory, // 招人渠道RejectedExecutionHandler handler) // 拒单方式

2.5 总结

Executors为我们提供了构造线程池的便捷方法,对于服务器程序我们应该杜绝使用这些便捷方法,而是直接使用线程池ThreadPoolExecutor的构造方法,避免无界队列可能导致的OOM以及线程个数限制不当导致的线程数耗尽等问题。ExecutorCompletionService提供了等待所有任务执行结束的有效方式,如果要设置等待的超时时间,则可以通过CountDownLatch完成。

二、 使用Executor创建线程池

2.1四种线程池

Java通过Executors提供四种线程池,分别为: 
1、newSingleThreadExecutor 
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
2、newFixedThreadPool 
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3、newScheduledThreadPool 
创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。 
4、newCachedThreadPoo 
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

2.2、使用场景详解

newSingleThreadExecutor:

  • 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无解阻塞队列
  • 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
  • 适用:一个任务一个任务执行的场景
  /** *创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行*/public static void singleTheadPoolTest() {ExecutorService pool = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {final int ii = i;pool.execute(() -> out.println(Thread.currentThread().getName() + "=>" + ii));}}-----output-------线程名称:pool-1-thread-1,执行0线程名称:pool-1-thread-1,执行1线程名称:pool-1-thread-1,执行2线程名称:pool-1-thread-1,执行3线程名称:pool-1-thread-1,执行4线程名称:pool-1-thread-1,执行5线程名称:pool-1-thread-1,执行6线程名称:pool-1-thread-1,执行7线程名称:pool-1-thread-1,执行8线程名称:pool-1-thread-1,执行9

newFixedThreadPool:

  • 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue() 无界阻塞队列
  • 通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
  • 适用:执行长期的任务,性能好很多
   /*** 1.创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小<br>* 2.线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程<br>* 3.因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字,和线程名称<br>*/public static void fixTheadPoolTest() {ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);for (int i = 0; i < 10; i++) {final int ii = i;fixedThreadPool.execute(() -> {out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii);try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}});}}
------output-------
线程名称:pool-1-thread-3,执行2
线程名称:pool-1-thread-1,执行0
线程名称:pool-1-thread-2,执行3
线程名称:pool-1-thread-3,执行4
线程名称:pool-1-thread-1,执行5
线程名称:pool-1-thread-2,执行6
线程名称:pool-1-thread-3,执行7
线程名称:pool-1-thread-1,执行8
线程名称:pool-1-thread-3,执行9

NewScheduledThreadPool:

  • 底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
  • 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
  • 适用:周期性执行任务的场景
/*** 创建一个定长线程池,支持定时及周期性任务执行。延迟执行*/public static void sceduleThreadPool() {ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);Runnable r1 = () -> out.println("线程名称:" + Thread.currentThread().getName() + ",执行:3秒后执行");scheduledThreadPool.schedule(r1, 3, TimeUnit.SECONDS);Runnable r2 = () -> out.println("线程名称:" + Thread.currentThread().getName() + ",执行:延迟2秒后每3秒执行一次");scheduledThreadPool.scheduleAtFixedRate(r2, 2, 3, TimeUnit.SECONDS);Runnable r3 = () -> out.println("线程名称:" + Thread.currentThread().getName() + ",执行:普通任务");for (int i = 0; i < 5; i++) {scheduledThreadPool.execute(r3);}}
----output------
线程名称:pool-1-thread-1,执行:普通任务
线程名称:pool-1-thread-5,执行:普通任务
线程名称:pool-1-thread-4,执行:普通任务
线程名称:pool-1-thread-3,执行:普通任务
线程名称:pool-1-thread-2,执行:普通任务
线程名称:pool-1-thread-1,执行:延迟2秒后每3秒执行一次
线程名称:pool-1-thread-5,执行:3秒后执行
线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次
线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次
线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次
线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次

newCachedThreadPool:

  • 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
  • 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
  • 适用:执行很多短期异步的小程序或者负载较轻的服务器
/*** 1.创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程<br>* 2.当任务数增加时,此线程池又可以智能的添加新线程来处理任务<br>* 3.此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小<br>* */public static void cacheThreadPool() {ExecutorService cachedThreadPool = Executors.newCachedThreadPool();for (int i = 1; i <= 10; i++) {final int ii = i;try {Thread.sleep(ii * 1);} catch (InterruptedException e) {e.printStackTrace();}cachedThreadPool.execute(()->out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii));}}-----output------
线程名称:pool-1-thread-1,执行1
线程名称:pool-1-thread-1,执行2
线程名称:pool-1-thread-1,执行3
线程名称:pool-1-thread-1,执行4
线程名称:pool-1-thread-1,执行5
线程名称:pool-1-thread-1,执行6
线程名称:pool-1-thread-1,执行7
线程名称:pool-1-thread-1,执行8
线程名称:pool-1-thread-1,执行9
线程名称:pool-1-thread-1,执行10

2.3 线程池任务执行流程

1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

2.4 备注

一般如果线程池任务队列采用LinkedBlockingQueue队列的话,那么不会拒绝任何任务(因为队列大小没有限制),这种情况下,ThreadPoolExecutor最多仅会按照最小线程数来创建线程,也就是说线程池大小被忽略了。如果线程池任务队列采用
  
ArrayBlockingQueue队列的话,那么ThreadPoolExecutor将会采取一个非常负责的算法,比如假定线程池的最小线程数为4,最大为8所用的ArrayBlockingQueue最大为10。随着任务到达并被放到队列中,线程池中最多运行4个线程(即最小线程数)。

即使队列完全填满,也就是说有10个处于等待状态的任务,ThreadPoolExecutor也只会利用4个线程。如果队列已满,而又有新任务进来,此时才会启动一个新线程,这里不会因为队列已满而拒接该任务,相反会启动一个新线程。新线程会运行队列中的

第一个任务,为新来的任务腾出空间。这个算法背后的理念是:该池大部分时间仅使用核心线程(4个),即使有适量的任务在队列中等待运行。这时线程池就可以用作节流阀。如果挤压的请求变得非常多,这时该池就会尝试运行更多的线程来清理;

这时第二个节流阀—最大线程数就起作用了。

来自:

Java线程池详解

java四种线程池的使用

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

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

相关文章

HTML5Plus

之前写过在 vue 中使用 mui 框架的方法&#xff0c;因为用 vue 开发后打包 5App 会有一些问题&#xff0c;所以当时用到了&#xff0c;最近又一次开发移动端&#xff0c;不同的是这次使用的是 vue3 开发的&#xff0c;导致之前使用的 vue-awesome-mui 依赖不能使用了&#xff0…

java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…

华为OD机考算法题:分奖金

题目部分 题目分奖金难度难题目说明公司老板做了一笔大生意&#xff0c;想要给每位员工分配一些奖金&#xff0c;想通过游戏的方式来决定每个人分多少钱。按照员工的工号顺序&#xff0c;每个人随机抽取一个数字。按照工号的顺序往后排列&#xff0c;遇到第一个数字比自己数字…

YOLOv5:对yolov5n模型进一步剪枝压缩

YOLOv5&#xff1a;对yolov5n模型进一步剪枝压缩 前言前提条件相关介绍具体步骤修改yolov5n.yaml配置文件单通道数据&#xff08;黑白图片&#xff09;修改models/yolo.py文件修改train.py文件 剪枝后模型大小 参考 前言 由于本人水平有限&#xff0c;难免出现错漏&#xff0c;…

chrome_elf.dll丢失怎么办?修复chrome_elf.dll文件的方法

Chrome是目前最受欢迎的网络浏览器之一&#xff0c;然而有时用户可能会遇到Chrome_elf.dll丢失的问题。该DLL文件是Chrome浏览器的一个重要组成部分&#xff0c;负责启动和管理程序的各种功能。当Chrome_elf.dll丢失时&#xff0c;用户可能无法正常启动Chrome或执行某些功能。本…

springboot jpa手动事务

创建springboot项目 搭建最简单的SpringBoot项目_Steven-Russell的博客-CSDN博客 引入jpa和数据据依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>…

手搓消息队列【RabbitMQ版】

什么是消息队列&#xff1f; 阻塞队列&#xff08;Blocking Queue&#xff09;-> 生产者消费者模型 &#xff08;是在一个进程内&#xff09;所谓的消息队列&#xff0c;就是把阻塞队列这样的数据结构&#xff0c;单独提取成了一个程序&#xff0c;进行独立部署~ --------&…

yolov8 模型部署--TensorRT部署-c++服务化部署

写目录 yolov8 模型部署--TensorRT部署1、模型导出为onnx格式2、模型onnx格式转engine 部署 yolov8 模型部署–TensorRT部署 1、模型导出为onnx格式 如果要用TensorRT部署YOLOv8&#xff0c;需要先使用下面的命令将模型导出为onnx格式&#xff1a; yolo export modelyolov8n.p…

【算法】常见位运算总结

目录 1.基础位运算2. 给一个数n&#xff0c;确定它的二进制表示中的第x位是0还是13.将一个数n的二进制表示的第x位修改成14.将一个数n的二进制表示的第x位修改成0、5. 位图的思想6.提取一个数(n)二进制表示中最右侧的17.干掉一个数(n)二进制表示中最右侧的18.位运算的优先级9.异…

华为OD七日集训第4期 - 按算法分类,由易到难,循序渐进,玩转OD

目录 一、适合人群二、本期训练时间三、如何参加四、7日集训第4期五、精心挑选21道高频100分经典题目&#xff0c;作为入门。第1天、数据结构第2天、滑动窗口第3天、贪心算法第4天、二分查找第5天、分治递归第6天、深度优先搜索dfs算法第7天、宽度优选算法&#xff0c;回溯法 六…

SpringBoot2.0(过滤器,监听器,拦截器)

目录 一&#xff0c;过滤器1.1&#xff0c;自定义Filter1.2&#xff0c;启动类代码1.2&#xff0c;创建filter类和LoginFilter包1.2.1&#xff0c;编写loginFilter类 过滤器代码1.2.2&#xff0c;创建二个Controller类 二&#xff0c;监听器2.1&#xff0c;自定义监听器2.2&…

Linux 操作系统云服务器安装部署 Tomcat 服务器详细教程

Tomcat 基本概述 Tomcat 服务器是Apache软件基金会&#xff08;Apache Software Foundation&#xff09;的 Jakarta 项目中的一个核心项目&#xff0c;由 Apache、Sun 和其他一些公司及个人共同开发而成。它是一个免费的开放源代码的 Web 应用服务器&#xff0c;属于轻量级应用…

DNS (Domain Name System) 域名解析过程

一、域名与IP地址 通常情况下一台电脑都会有一个IPv4和IPv6地址&#xff08;由数字和字母组成&#xff0c;难以记忆&#xff09;&#xff0c;所以日常访问网站时我们通常都是采用输入域名&#xff08;方便记忆&#xff09;的方式来访问。 二、域名结构树 www 主机名bilibil…

宋浩高等数学笔记(十二)无穷级数

完结&#xff0c;宋浩笔记系列的最后一更~ 之后会出一些武忠祥老师的错题&笔记总结&#xff0c;10月份就要赶紧做真题了

End-to-End Object Detection with Transformers(论文解析)

End-to-End Object Detection with Transformers 摘要介绍相关工作2.1 集合预测2.2 transformer和并行解码2.3 目标检测 3 DETR模型3.1 目标检测集设置预测损失3.2 DETR架构 摘要 我们提出了一种将目标检测视为直接集合预测问题的新方法。我们的方法简化了检测流程&#xff0c…

C# OpenVino Yolov8 Seg 分割

效果 项目 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using OpenCvSharp;namespace OpenVino_Yolov8_Demo {public…

WorkPlus AI助理,基于ChatGPT的企业级知识问答机器人

随着人工智能技术的发展&#xff0c;WorkPlus AI助理以ChatGPT对话能力为基础&#xff0c;将企业数据与人工智能相结合&#xff0c;推出了面向企业的知识问答机器人。这一创新性的解决方案帮助企业高效管理和利用自身的知识资产&#xff0c;助力企业级人工智能的构建。与传统的…

腾讯云和阿里云2核2G服务器租用价格表对比

2核2G云服务器可以选择阿里云服务器或腾讯云服务器&#xff0c;腾讯云轻量2核2G3M带宽服务器95元一年&#xff0c;阿里云轻量2核2G3M带宽优惠价108元一年&#xff0c;不只是轻量应用服务器&#xff0c;阿里云还可以选择ECS云服务器u1&#xff0c;腾讯云也可以选择CVM标准型S5云…

如何在 Ubuntu 上安装和使用 Nginx?

ginx&#xff08;发音为“engine-x”&#xff09;是一种流行的 Web 服务器软件&#xff0c;以其高性能和可靠性而闻名。它是许多流行网站使用的开源软件&#xff0c;包括 Netflix、GitHub 和 WordPress。Nginx 可以用作 Web 服务器、负载均衡器、反向代理和 HTTP 缓存等。 它以…

2.linux的组管理和权限管理

一、组管理 1.Linux组的介绍 在linux中每个用户必须属于一个组&#xff0c;不能独立于组外。在linux中每个文件有所有者&#xff0c;所有组&#xff0c;其他组的概念 ①所有者 ②所在组 ③其他组 ④改变用户所在组 2.文件/目录 所有者 哪个用户创建了文件&#xff0c;就…