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

万字详解,Java实现低配版线程池

文章目录

  • 1.什么是线程池
  • 2.线程池的优势
  • 3.原理
  • 4.代码编写
    • 4.1 阻塞队列
    • 4.2 ThreadPool线程池
    • 4.3 Worker工作线程
    • 4.4 代码测试
  • 5. 拒绝策略
    • 5.1 抽象Reject接口
    • 5.2 BlockingQueue新增tryPut方法
    • 5.3 修改ThreadPool的execute方法
    • 5.4 ThreadPool线程池构造函数修改
    • 5.5 拒绝策略实现
      • 1. 丢弃策略
      • 2. 移除最老元素
      • 3. 死等
      • 4. 抛出异常
    • 5.6 代码测试
  • 6.全部代码

1.什么是线程池

线程池,通过创建了一定数量的线程并将其维护在一个池(即容器)中。当有新的任务提交时,线程池会从池子中分配一个空闲线程来执行任务,而不是每次都新建线程。执行完任务后,线程不会被销毁,而是返回到线程池中等待下一个任务。

2.线程池的优势

  1. 避免线程的重复创建与销毁:对于需要执行的任务,如果每个任务都需要创建一个线程,线程执行完任务后销毁,那么会极大的造成资源的浪费。一方面任务数量可能会很庞大,创建与之匹配的线程会对内存造成严重消耗;另一方面,创建完的线程只工作一次,资本家看了落泪,md血亏啊
  2. 降低资源消耗:创建的线程反复利用,避免了创建与销毁带来的开销
  3. 提高工作的准备时间:被提交的任务可以迅速被线程池中存储的线程执行,无需重新创建

3.原理

线程池中存在以下核心组件

  • 线程池容器(存储工作线程)
  • 任务队列(存储需要执行的任务)

下述代码中,线程池使用HashSet存储;任务队列,使用的是这篇文章实现的BlockingQueue阻塞队列

另外,单纯的Thread线程能够存储的信息太少,因此我们创建Worker对象,extents Thread来包装Thread

下图是线程池的工作流程
在这里插入图片描述
大体来说,线程池执行逻辑分为三大步骤

  1. 如果current thread number < coreSize,创建核心线程执行任务

    tip:

    • current thread number在源码中,是有一个AtomicInteger变量ctl表示。ctl是核心线程池状态控制器,它被分为两个组成部分。其中,高三位表示runStatus,线程池状态;低三位表示workCount,工作线程数量。
    • 选择一个变量ctl同时存储runStatus和workCount,可以通过一次CAS操作实现原子赋值,而不用两次。
  2. 如果核心线程创建失败,或者核心线程数量过多,则将任务存储在阻塞队列中:在这一步中,存在非常多的细节。
    2.1. 如果当前线程池不处于RUNNING状态,尝试创建救急线程运行,不执行入队操作
    2.2. 如果入队失败,同样创建救急线程
    2.3. 如果线程池处于运行状态,且入队成功。进行double-check,重新检查线程池状态ctl
    2.4. 如果此时线程池不处于RUNNIG状态,移除刚入队的任务,并执行reject策略
    2.5. 如果线程池依然处于RUNNIG状态,且工作线程为0,创建救急线程,执行任务
  3. 如果上述步骤均失败,创建救济线程,如果依然失败,执行reject策略

4.代码编写

4.1 阻塞队列

实现请看BlockingQueue阻塞队列,本文不再赘述

4.2 ThreadPool线程池

/*** 线程池*/
@Slf4j
public class ThreadPool {// 核心线程数private int coreSize;// 阻塞队列private BlockingQueue<Runnable> workQueue;// 队列容量private int capacity;// 工作线程private final HashSet<Worker> workers = new HashSet<>();// todo: Worker(详见下一部分)private final class Worker extents Thread { /*...*/ }public ThreadPool(int coreSize, int capacity) {this.coreSize = coreSize;this.capacity = capacity;this.workQueue = new BlockingQueue<>(capacity);}/*** 执行task任务. 如果当前线程数量 < coreSize, 创建线程执行* 否则加入阻塞队列. 如果阻塞队列已满, 执行当前拒绝策略* @param task 需要执行任务*/public void execute(Runnable task) {if (task == null)throw new NullPointerException("task is null");synchronized (workers) {if (workers.size() < coreSize) {// 创建线程执行(我们倾向于创建新线程来执行任务, 而非已创建线程)log.info("创建worker");Worker worker = new Worker(task);workers.add(worker);worker.start(); // 千万别写成调用run方法, 否则主线程会阻塞(run不会开启线程)}else {log.info("添加阻塞队列");// 添加阻塞队列workQueue.put(task);}}}
}

上述代码实现简易版线程池。

  • workQueue:阻塞队列,用于存储待执行的任务
  • coreSize:核心线程数量
  • capacity:阻塞队列大小
  • workers:工作线程的存储容器(线程池),用HashSet实现。请注意,HashSet是线程不安全的,因此在对HashSet操作时,记得加锁保证不会出现并发问题

本节对execute执行逻辑进行一定的简化,暂时不考虑拒绝策略(后续介绍)。

  • 如果当前线程数量 < coreSize,创建核心线程并执行任务
  • 否则添加阻塞队列

tip: 如果任务数量超过阻塞队列容量,那么依据阻塞队列的性质,后续的所有线程都会阻塞,等待容量减少。

4.3 Worker工作线程

我们使用包装过后的线程对象。且Worker是ThreadPool的内部类

private final class Worker extends Thread {// 执行的任务private Runnable task;Worker(Runnable task) {this.task = task;}/*** 执行task任务, 如果task为null, 则从workQueue工作队列中获取任务* 如果工作队列中不存在等待执行的任务, 终止当前Worker工作线程*/@Overridepublic void run() {while (task != null || (task = workQueue.take()) != null) {try {log.info("运行任务");task.run();} catch (Exception e) {e.printStackTrace();} finally {task = null;}}// 移除当前工作线程synchronized (workers) {workers.remove(this);}}
}

为了简化代码编写,本文只存在核心线程。核心线程的工作是监视阻塞队列,获取待执行的任务并执行

run方法中,while循环的条件有二

  • task != null: worker线程创建时,会分配第一个待执行的任务。如果待执行的任务不为null,则执行任务
  • task = workQueue.take():worker线程持续监视workQueue阻塞队列中的任务,如果存在任务,获取并执行

tip: workQueue.take()是一个阻塞的方法,没有时间的限制。也就是说,哪怕workQueue为空,该方法也会死等下去

4.4 代码测试

import lombok.extern.slf4j.Slf4j;@Slf4j
public class Test {public static void main(String[] args) throws InterruptedException {ThreadPool threadPool = new ThreadPool(2, 5);for (int i = 0; i < 10; i++) {int j = i;// 任务创建时间为2s, 任务消费时间显著低于任务创建时间.// 因此本模型是个典型的快生产, 慢消费的模型threadPool.execute(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}log.info(String.valueOf(j));});}}
}

控制台输出

21:07:34.189 [main] INFO com.fgbg.juc.ThreadPool - 创建worker
21:07:34.202 [main] INFO com.fgbg.juc.ThreadPool - 创建worker
21:07:34.202 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:07:34.205 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:07:34.205 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:07:34.205 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:07:34.205 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:07:34.205 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:07:34.203 [Thread-0] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:34.209 [Thread-1] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:36.223 [Thread-0] INFO com.fgbg.juc.Test - 0
21:07:36.223 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:07:36.223 [Thread-0] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:36.239 [Thread-1] INFO com.fgbg.juc.Test - 1
21:07:36.240 [Thread-1] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:36.240 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:07:38.239 [Thread-0] INFO com.fgbg.juc.Test - 2
21:07:38.239 [Thread-0] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:38.256 [Thread-1] INFO com.fgbg.juc.Test - 3
21:07:38.256 [Thread-1] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:40.250 [Thread-0] INFO com.fgbg.juc.Test - 4
21:07:40.250 [Thread-0] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:40.265 [Thread-1] INFO com.fgbg.juc.Test - 5
21:07:40.266 [Thread-1] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:42.252 [Thread-0] INFO com.fgbg.juc.Test - 6
21:07:42.252 [Thread-0] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:42.268 [Thread-1] INFO com.fgbg.juc.Test - 7
21:07:42.268 [Thread-1] INFO com.fgbg.juc.ThreadPool - 运行任务
21:07:44.260 [Thread-0] INFO com.fgbg.juc.Test - 8
21:07:44.275 [Thread-1] INFO com.fgbg.juc.Test - 9

线程池核心线程数为2,因此一开始迅速创建2个worker线程。但因为阻塞队列容量为5,且每个线程工作需要2s,耗时远远小于任务产出的速度,因此队列被迅速沾满

当提交第8个任务时,主线程进入阻塞状态,无法继续提交任务(2个任务正在执行 + 5个任务添加阻塞队列 + 1个任务刚要入队,就阻塞了)

当第一个任务被执行完成,Thread-0 Worker执行阻塞队列中的其他任务。此时存在多余位置,之前被阻塞主线程成功提交任务,并继续循环

后续的流程大体一致,故不在做多余分析。

5. 拒绝策略

所谓拒绝策略,就是提供给调用方一个选择。如果调用方提交了过量的任务,多余的任务作何种处理。

由上方代码分析可知,我们一开始对于过量的任务,处理方案就是死等。但这种方案无法满足其他特定的需求,比如某个场景对执行速度有要求,等待一段时间后阻塞队列依然无法处理额外的任务,那么主线程就要抛弃该任务。死等是处理的方式之一,但存在不少的局限性,我们需要更多的处理方式。

对于不同的处理方式,我们可以选择将代码写死在ThreadPool中,但这样太不灵活,对于不同的场景,我们需要添加大量if else。因此我们可以采用策略模式,将拒绝的行为抽象成一个接口,创建ThreadPool时,由调用方传递接口。这样我们就可以在不改变ThreadPool内部代码的同时,改变ThreadPool面对超量任务的拒绝行为

5.1 抽象Reject接口

@FunctionalInterface
public interface RejectPolicy {// 执行拒绝策略void reject(Runnable task, BlockingQueue<Runnable> workQueue);
}

5.2 BlockingQueue新增tryPut方法

tryPut方法,尝试将元素立刻添加到阻塞队列中,不支持阻塞等待

// 尝试立即添加元素
public boolean tryPut(T task) {lock.lock();try {if (deque.size() == capacity) return false;deque.addLast(task);return true;} finally {lock.unlock();}
}

5.3 修改ThreadPool的execute方法

execute执行task入队操作时,如果入队失败(阻塞队列已满),则调用reject执行拒绝策略

    public void execute(Runnable task) {if (task == null)throw new NullPointerException("task is null");synchronized (workers) {if (workers.size() < coreSize) {// 创建线程执行(我们倾向于创建新线程来执行任务, 而非已创建线程)log.info("创建worker");Worker worker = new Worker(task);workers.add(worker);worker.start(); // 千万别写成调用run方法, 否则主线程会阻塞(run不会开启线程)}else {log.info("添加阻塞队列");/*----------------modify below-------------------*/// 添加阻塞队列// workQueue.put(task);// 添加失败if ( !workQueue.tryPut(task)) {// 执行拒绝策略rejectPolicy.reject(task, workQueue);}}}}

5.4 ThreadPool线程池构造函数修改

    // 拒绝策略private RejectPolicy rejectPolicy;public ThreadPool(int coreSize, int capacity, RejectPolicy rejectPolicy) {this(coreSize, capacity);this.rejectPolicy = rejectPolicy;}

5.5 拒绝策略实现

因为RejectPolicy接口有@FunctionalInterface,支持lambda表达式,因此编写的时候可以简写

1. 丢弃策略

(task, workQueue) -> {}

2. 移除最老元素

(task, workQueue) -> { workQueue.poll(); }

tip: 笔者自定义的BlockingQueue没有实现poll方法,各位读者如果感兴趣,可以自行实现。需要注意的是,记得加锁保证线程安全

3. 死等

(task, workQueue) -> { workQueue.put(task); }

4. 抛出异常

(task, workQueue) -> { new RuntimeException("workQueue is full"); }

5.6 代码测试

@Slf4j
public class Test3 {public static void main(String[] args) throws InterruptedException {ThreadPool threadPool = new ThreadPool(2, 5, (task, workQueue) -> {log.info("任务丢弃");});for (int i = 0; i < 10; i++) {int j = i;threadPool.execute(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}log.info(String.valueOf(j));});}}
}

上述代码选择的拒绝策略是丢弃

控制台输出

21:46:37.621 [main] INFO com.fgbg.juc.ThreadPool - 创建worker
21:46:37.630 [main] INFO com.fgbg.juc.ThreadPool - 创建worker
21:46:37.631 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:46:37.631 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:46:37.631 [Thread-0] INFO com.fgbg.juc.ThreadPool - 运行任务
21:46:37.631 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:46:37.631 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:46:37.631 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:46:37.631 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:46:37.631 [main] INFO com.fgbg.juc.Test3 - 任务丢弃
21:46:37.631 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:46:37.631 [main] INFO com.fgbg.juc.Test3 - 任务丢弃
21:46:37.632 [main] INFO com.fgbg.juc.ThreadPool - 添加阻塞队列
21:46:37.632 [main] INFO com.fgbg.juc.Test3 - 任务丢弃
21:46:37.633 [Thread-1] INFO com.fgbg.juc.ThreadPool - 运行任务
21:46:39.636 [Thread-1] INFO com.fgbg.juc.Test3 - 1
21:46:39.636 [Thread-0] INFO com.fgbg.juc.Test3 - 0
21:46:39.636 [Thread-0] INFO com.fgbg.juc.ThreadPool - 运行任务
21:46:39.636 [Thread-1] INFO com.fgbg.juc.ThreadPool - 运行任务
21:46:41.644 [Thread-0] INFO com.fgbg.juc.Test3 - 3
21:46:41.645 [Thread-0] INFO com.fgbg.juc.ThreadPool - 运行任务
21:46:41.644 [Thread-1] INFO com.fgbg.juc.Test3 - 2
21:46:41.645 [Thread-1] INFO com.fgbg.juc.ThreadPool - 运行任务
21:46:43.651 [Thread-1] INFO com.fgbg.juc.Test3 - 5
21:46:43.651 [Thread-0] INFO com.fgbg.juc.Test3 - 4
21:46:43.651 [Thread-1] INFO com.fgbg.juc.ThreadPool - 运行任务
21:46:45.657 [Thread-1] INFO com.fgbg.juc.Test3 - 6

由日志可知,第8,9,10号任务被丢弃。任务对应的输出为7,8,9。观察输出的数字,发现最大值为6。因此确认了7~10号任务全部被拒绝,测试成功

6.全部代码

BlockingQueue


import java.util.ArrayDeque;
import java.util.Deque;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;// 消息队列(阻塞队列)
class BlockingQueue<T> {// 队列private Deque<T> deque = new ArrayDeque<>();// 容量private int capacity;// 锁private final ReentrantLock lock = new ReentrantLock();// 消费者等待条件private Condition consumerWaitSet = lock.newCondition();// 生产者等待条件private Condition producerWaitSet = lock.newCondition();public BlockingQueue(int capacity) {this.capacity = capacity;}// 添加元素public void put(T element) {lock.lock();try {// 队列已满while (deque.size() == capacity) {try {// 阻塞等待producerWaitSet.await();} catch (InterruptedException e) {}}// 添加元素deque.addLast(element);// 唤醒其它线程consumerWaitSet.signal();} finally {lock.unlock();}}// 获取元素public T take() {lock.lock();try {// 判空while (deque.size() == 0) {try {// 阻塞等待consumerWaitSet.await();} catch (InterruptedException e) {}}// 获取元素T res = deque.pollFirst();producerWaitSet.signal();return res;} finally {lock.unlock();}}// 尝试立即添加元素public boolean tryPut(T task) {lock.lock();try {if (deque.size() == capacity) return false;deque.addLast(task);return true;} finally {lock.unlock();}}
}

RejectPolicy

@FunctionalInterface
public interface RejectPolicy {// 执行拒绝策略void reject(Runnable task, BlockingQueue<Runnable> workQueue);
}

ThreadPool

import lombok.extern.slf4j.Slf4j;import java.util.HashSet;/*** 线程池*/
@Slf4j
public class ThreadPool {// 核心线程数private int coreSize;// 阻塞队列private BlockingQueue<Runnable> workQueue;// 队列容量private int capacity;// 工作线程private final HashSet<Worker> workers = new HashSet<>();// 拒绝策略private RejectPolicy rejectPolicy;private final class Worker extends Thread {// 执行的任务private Runnable task;Worker(Runnable task) {this.task = task;}/*** 执行task任务, 如果task为null, 则从workQueue工作队列中获取任务* 如果工作队列中不存在等待执行的任务, 终止当前Worker工作线程*/@Overridepublic void run() {while (task != null || (task = workQueue.take()) != null) {try {log.info("运行任务");task.run();} catch (Exception e) {e.printStackTrace();} finally {task = null;}}// 移除当前工作线程synchronized (workers) {workers.remove(this);}}}public ThreadPool(int coreSize, int capacity) {this.coreSize = coreSize;this.capacity = capacity;this.workQueue = new BlockingQueue<>(capacity);}public ThreadPool(int coreSize, int capacity, RejectPolicy rejectPolicy) {this(coreSize, capacity);this.rejectPolicy = rejectPolicy;}/*** 执行task任务. 如果当前线程数量 < coreSize, 创建线程执行* 否则加入阻塞队列. 如果阻塞队列已满, 执行当前拒绝策略* @param task 需要执行任务*/public void execute(Runnable task) {if (task == null)throw new NullPointerException("task is null");synchronized (workers) {if (workers.size() < coreSize) {// 创建线程执行(我们倾向于创建新线程来执行任务, 而非已创建线程)log.info("创建worker");Worker worker = new Worker(task);workers.add(worker);worker.start(); // 千万别写成调用run方法, 否则主线程会阻塞(run不会开启线程)}else {log.info("添加阻塞队列");// 添加阻塞队列// workQueue.put(task);// 添加失败if ( !workQueue.tryPut(task)) {// 执行拒绝策略rejectPolicy.reject(task, workQueue);}}}}
}

Test

import lombok.extern.slf4j.Slf4j;@Slf4j
public class Test {public static void main(String[] args) throws InterruptedException {ThreadPool threadPool = new ThreadPool(2, 5);for (int i = 0; i < 10; i++) {int j = i;threadPool.execute(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}log.info(String.valueOf(j));});}}
}

Test3

@Slf4j
public class Test3 {public static void main(String[] args) throws InterruptedException {ThreadPool threadPool = new ThreadPool(2, 5, (task, workQueue) -> {log.info("任务丢弃");});for (int i = 0; i < 10; i++) {int j = i;threadPool.execute(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}log.info(String.valueOf(j));});}}
}

相关文章:

万字详解,Java实现低配版线程池

文章目录 1.什么是线程池2.线程池的优势3.原理4.代码编写4.1 阻塞队列4.2 ThreadPool线程池4.3 Worker工作线程4.4 代码测试 5. 拒绝策略5.1 抽象Reject接口5.2 BlockingQueue新增tryPut方法5.3 修改ThreadPool的execute方法5.4 ThreadPool线程池构造函数修改5.5 拒绝策略实现1…...

挂耳式蓝牙耳机哪家的好用?购买耳机前必须了解的几大要点

随着健康意识的提升&#xff0c;越来越多的人开始热衷于运动。运动不仅能够增强体质&#xff0c;对于我们这些忙碌的上班族而言&#xff0c;它也是一种极佳的减压方式。经过一天的辛勤工作&#xff0c;能够在户外跑步&#xff0c;让汗水带走压力&#xff0c;实在是一种享受。在…...

CSS文本属性

CSS文本属性 1.文本颜色2.文本间距3. 文本修饰4 .文本缩进5.文本对齐_水平6.行高7. vertical-align 1.文本颜色 属性名&#xff1a;color作用&#xff1a;控制文字的颜色。可选值&#xff1a; 颜色名rgb或rgbaHEX或HEXA &#xff08;十六进制&#xff09;HSL或HSLA 开发中常用…...

MySQL篇—执行计划之覆盖索引Using index和条件过滤Using where介绍(第三篇,总共三篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…...

最短路径(2.19)

目录 1.网络延迟时间 弗洛伊德算法 迪杰斯特拉算法 2. K 站中转内最便宜的航班 3.从第一个节点出发到最后一个节点的受限路径数 4.到达目的地的方案数 1.网络延迟时间 有 n 个网络节点&#xff0c;标记为 1 到 n。 给你一个列表 times&#xff0c;表示信号经过 有向 边的…...

vue 总结

1.vue 的生命周期 1. es6 2. vue 基本属性指令 <template><div><!--<h1>vue基本指令的使用方式</h1><a :href"url">v-bind使用链接</a><img :src"srcUrl" /><div>解决闪烁问题<p v-cloak>{{…...

深入理解TCP/IP协议:互联网通信的核心

深入理解TCP/IP协议&#xff1a;互联网通信的核心 在数字化时代&#xff0c;TCP/IP协议是支撑全球互联网通信的基石。它不仅负责数据的传输和路由&#xff0c;还确保了信息传递的准确性和完整性。本文将深入探讨TCP/IP协议的工作原理、结构以及它在网络编程中的应用。 TCP/IP…...

Python数据处理实战(4)-上万行log数据提取并作图进阶版

系列文章&#xff1a; 0、基本常用功能及其操作 1&#xff0c;20G文件&#xff0c;分类&#xff0c;放入不同文件&#xff0c;每个单独处理 2&#xff0c;数据的归类并处理 3&#xff0c;txt文件指定的数据处理并可视化作图 4&#xff0c;上万行log数据提取并作图进阶版&a…...

JavaWeb Tomcat启动、部署、配置、集成IDEA

web服务器软件 服务器是安装了服务器软件的计算机&#xff0c;在web服务器软件中&#xff0c;可以部署web项目&#xff0c;让用户通过浏览器来访问这些项目。 Web服务器是一个应用程序&#xff08;软件&#xff09;&#xff0c;对HTTP协议的操作进行封装&#xff0c;使得程序…...

关于Vue3的一些操作

1. 设置浏览器自动打开 在package.json 中设置 dev: vite --open 2.给src文件夹配置别名 在vite.config.ts配置文件中添加以下内容 3. 如果2中有红色波浪线的问题 ***安装一个文件包***npm install types/node3. 在tsconfig.json配置文件中&#xff0c;找到配置项compi…...

外贸常用的出口认证 | 全球外贸数据服务平台 | 箱讯科技

出口认证是一种贸易信任背书&#xff0c;对许多外贸从业者而言,产品的出口认证和当前的国际贸易环境一样复杂多变&#xff0c;不同的目标市场、不同的产品类别,所需要的认证及标准也不同。 国际认证 01 IECEE-CB IECEE-CB体系的中文含义是“关于电工产品测试证书的相互认可体…...

C++ 标准库类型string

C/C总述&#xff1a;Study C/C-CSDN博客 目录 定义和初始化string对象 string的增 使用push_back进行尾插 使用insert插入 使用append函数完成string的拼接 string的删 使用pop_back进行尾删 使用erase删除 string的查 使用find函数正向搜索第一个匹配项 使用rf…...

Material UI 5 学习02-其它按钮组件

Material UI 5 学习02-其它按钮组件 一、IconButton按钮二、 ButtonGroup按钮组1、最基本的实例2、垂直按钮组 一、IconButton按钮 图标按钮通常适用于切换按钮&#xff0c;允许选择或选择单个选项 取消选择&#xff0c;例如在项目中添加或删除星号。 <IconButton aria-lab…...

Express学习(三)

Express中间件 中间件的概念 什么是中间件 中间件&#xff0c;特指业务流程的中间处理环节。Express中间件的调用流程 当一个请求到达Express的服务器之后&#xff0c;可以连续调用多个中间件&#xff0c;从而对这次请求进行预处理。类似于下图所示 Express中间件的格式 Expr…...

influxdb2.0插入数据字段类型出现冲突问题解决

一、问题出现 一个学校换热站自控系统&#xff0c;会定时从换热站获取测点数据&#xff0c;并插入到influxdb数据库中。influxdb插入数据时&#xff0c;报错提示&#xff1a; com.influxdb.exceptions.UnprocessableEntityException: failure writing points to database: par…...

[C++]类和对象,explicit,static,友元,构造函数——喵喵要吃C嘎嘎4

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;大大会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…...

物联网的商业模式洞察

大约在十年前&#xff08;2014年11月&#xff09;&#xff0c;全球知名管理思想家、哈佛商学院教授迈克尔波特与PTC前首席执行官吉姆赫普尔曼&#xff0c;在《哈佛商业评论》上联合撰写了一篇备受赞誉的文章&#xff0c;题为《智能互联产品如何改变竞争》。在这篇文章中&#x…...

智能指针基础知识【C++】【RAII思想 || unique_ptr || shared_ptrweak_ptr || 循环引用问题】

目录 一&#xff0c;为什么需要智能指针 二&#xff0c;内存泄露的基本认识 1. 内存泄露分类 2. 常见的内存检测工具 3&#xff0c;如何避免内存泄露 三&#xff0c;智能指针的使用与原理 1. RAII思想 2. 智能指针 &#xff08;1. unique_ptr &#xff08;2. shared_…...

leetcode:反转链表II 和k个一组反转链表的C++实现

反转链表II 问题描述 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 。 ListNode* reverseBetween(ListNode* head, int left, int right) {ListNode *…...

ERD Online 快速启动指南:代码下载到首次运行的全流程攻略 ️

&#x1f680; 一、代码下载 ERD online前端代码正常拉取即可&#x1f44c; 后端代码含有子模块&#xff0c;拉取命令如下&#xff1a; git clone --recurse-submodules https://github.com/www-zerocode-net-cn/martin-framework.git &#x1f6e0;️ 二、代码构建 &#x1f3…...

c++ 11 新特性 不同数据类型之间转换函数之const_cast

一.不同数据类型之间转换函数const_cast介绍 const_cast是C11中引入的一种类型转换操作符&#xff0c;用于修改类型的const或volatile属性。const_cast的主要用途是移除对象的常量性&#xff0c;它是唯一具有此能力的C风格的转型操作符。在C11中&#xff0c;const_cast可以完成…...

C++从零开始的打怪升级之路(day45)

这是关于一个普通双非本科大一学生的C的学习记录贴 在此前&#xff0c;我学了一点点C语言还有简单的数据结构&#xff0c;如果有小伙伴想和我一起学习的&#xff0c;可以私信我交流分享学习资料 那么开启正题 今天分享的是关于二叉树的题目 1.根据二叉树创建字符串 606. 根…...

小鹅通前端实习一面

总时长35分钟&#xff0c;自我介绍开始 1.js和c特点上的差异&#xff1b; 2.js数组去重 3.js的数据类型 4.js的引用类型和值类型的差别 5.讲一下js的网络请求 6.对前端三件套和框架的理解 7.一个html文档的结构是怎样的 8.head和body的区别 9.一个页面的加载顺序&#xff08;ht…...

ArrayList常用API

常见方法 add 增remove 删set 改get 查clear 清空元素size 长度isEmpty 为空判断 用法 // String就是泛型 这种使用方法对于限制类型很有用 ArrayList<String> arrayList new ArrayList<>();// add 添加元素 返回的是boolean 代表是否添加成功 arrayList.add(&qu…...

Chrome安装Axure插件

打开原型目录/resources/chrome&#xff0c;重命名axure-chrome-extension.crx&#xff0c;修改后缀为rar&#xff0c;axure-chrome-extension.rar 解压到axure-chrome-extension目录打开Chrome&#xff0c;更多工具->扩展程序&#xff0c;打开开发者模式&#xff0c;选择加…...

【AI+应用】模仿爆款视频二次创作短视频操作步骤

本来不想水这篇的&#xff0c; 剪辑软件估计很多人用的比我还6。 今天自己遇到1个需求&#xff0c;我看到一篇公众号文章的视频觉得有意思&#xff0c;但视频有点长&#xff0c;我没带耳机看视频的习惯&#xff0c;就想着能不能下载下来&#xff0c; 提取视频的音频转为文字&am…...

HTML使用

文章目录 一、简介二、HTML快速入门三、基础标签四、图片、音频、视频标签五、超链接标签六、列表标签七、表格标签八、布局标签九、表单标签十、表单向标签 一、简介 二、HTML快速入门 ​ <html><head><title>你好</title></head><body>再…...

通过联合部署DDoS高防和WAF提升网站防护能力

如果您的网站遭受的攻击既有流量型攻击&#xff0c;又混杂精巧的Web应用层攻击时&#xff08;例如SQL注入、跨站脚本攻击、命令注入等&#xff09;时&#xff0c;推荐您组合使用阿里云DDoS高防和Web 应用防火墙 WAF&#xff08;Web Application Firewall&#xff09;&#xff0…...

具体挫折现象的发生以及解法思考:您如果继续不问的话,严重重责就容易来

一 积极想方设法的寻找扭转劣势的方式方法&#xff1b;  目前对于第一条的践行&#xff0c;主要还是依靠打工做事赚取收入。至于个人业务&#xff0c;只能往后推&#xff0c;往后延迟。因为不管您目前居住的环境&#xff0c;还是个人条件都不行&#xff0c;所以无法实行个人业…...

Type-C接口PD协议统一:引领电子科技新纪元的优势解析

在电子科技日新月异的今天&#xff0c;充电接口的统一化已经成为了业界的一大趋势。其中&#xff0c;Type-C接口凭借其传输速度快、使用便捷等优点&#xff0c;迅速成为了市场上的主流选择。而PD&#xff08;Power Delivery&#xff09;协议的统一&#xff0c;更是为Type-C接口…...