10分钟从实现和使用场景聊聊并发包下的阻塞队列
上篇文章12分钟从Executor自顶向下彻底搞懂线程池中我们聊到线程池,而线程池中包含阻塞队列
这篇文章我们主要聊聊并发包下的阻塞队列
阻塞队列
什么是队列?
队列的实现可以是数组、也可以是链表,可以实现先进先出的顺序队列,也可以实现先进后出的栈队列
那什么是阻塞队列?
在经典的生产者/消费者模型中,生产者们将生产的元素放入队列,而消费者们从队列获取元素消费
当队列已满,我们会手动阻塞生产者,直到消费者消费再来手动唤醒生产者
当队列为空,我们会手动阻塞消费者,直到生产者生产再来手动唤醒消费者
在这个过程中由于使用的是普通队列,阻塞与唤醒我们需要手动操作,保证同步机制
阻塞队列在队列的基础上提供等待/通知功能,用于线程间的通信,避免线程竞争死锁
生产者可以看成往线程池添加任务的用户线程,而消费者则是线程池中的工作线程
当阻塞队列为空时阻塞工作线程获取任务,当阻塞队列已满时阻塞用户线程向队列中添加任务(创建非核心线程、拒绝策略)
API
阻塞队列提供一下四种添加、删除元素的API,我们常用阻塞等待/超时阻塞等待的API
方法名 | 抛出异常 | 返回true/false | 阻塞等待 | 超时阻塞等待 |
---|---|---|---|---|
添加 | add(Object) | offer(Object) | put(Object) | offer(Object,long,TimeUnit) |
删除 | remove() | poll() | take() | poll(long,TimeUnit) |
- 抛出异常:队满add 抛出异常
IllegalStateExceptio
;队空remove 抛出异常NoSuchElementException
- 返回值: 队满offer返回false,队空poll返回null
- 阻塞等待: 队满时put会阻塞线程 或 队空时take会阻塞线程
- 超时阻塞等待: 在阻塞等待、返回true/false的基础上增加超时等待(等待一定时间就退出等待)
阻塞队列的公平与不公平
什么是阻塞队列的公平与不公平?
当阻塞队列已满时,如果是公平的,那么阻塞的线程根据先后顺序从阻塞队列中获取元素,不公平则反之
实际上阻塞队列的公平与不公平,要看实现阻塞队列的锁是否公平
阻塞队列一般默认使用不公平锁
ArrayBlockingQueue
从名称看就可以知道它是数组实现的,我们先来看看它有哪些重要字段
public class ArrayBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {//存储元素的数组final Object[] items;//记录元素出队的下标int takeIndex;//记录元素入队的下标int putIndex;//队列中元素数量int count;//使用的锁final ReentrantLock lock;//出队的等待队列,作用于消费者private final Condition notEmpty;//入队的等待队列,作用于生产者private final Condition notFull;}
看完关键字段,我们可以知道:ArrayBlockingQueue
由数组实现、使用并发包下的可重入锁、同时用两个等待队列作用生产者和消费者
为什么出队、入队要使用两个下标记录?
实际上它是一个环形数组,在初始化后就不改变大小,后续查看源码自然能明白它是环形数组
在构造器中、初始化数组容量,同时使用非公平锁
public ArrayBlockingQueue(int capacity) {this(capacity, false);}public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity <= 0)throw new IllegalArgumentException();this.items = new Object[capacity];//锁是否为公平锁lock = new ReentrantLock(fair);notEmpty = lock.newCondition();notFull = lock.newCondition();}
ArrayBlockingQueue的公平性是由ReentrantLock来实现的
我们来看看入队方法,入队方法都大同小异,我们本文都查看支持超时、响应中断的方法
public boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException {//检查空指针checkNotNull(e);//获取超时纳秒long nanos = unit.toNanos(timeout);final ReentrantLock lock = this.lock;//加锁lock.lockInterruptibly();try {//如果队列已满while (count == items.length) {//超时则返回入队失败,否则生产者等待对应时间if (nanos <= 0)return false;nanos = notFull.awaitNanos(nanos);}//入队enqueue(e);return true;} finally {//解锁lock.unlock();}}
直接使用可重入锁保证同步,如果队列已满,在此期间判断是否超时,超时就返回,未超时等待;未满则执行入队方法
private void enqueue(E x) {//队列数组final Object[] items = this.items;//往入队下标添加值items[putIndex] = x;//自增入队下标 如果已满则定位到0 成环if (++putIndex == items.length)putIndex = 0;//统计数量增加count++;//唤醒消费者notEmpty.signal();}
在入队中,主要是添加元素、修改下次添加的下标、统计队列中的元素和唤醒消费者,到这以及可以说明它的实现是环形数组
ArrayBlockingQueue
由环形数组实现的阻塞队列,固定容量不支持动态扩容,使用非公平的ReertrantLock
保证入队、出队操作的原子性,使用两个等待队列存储等待的生产者、消费者,适用于在并发量不大的场景
LinkedBlockingQueue
LinkedBlockingQueue
从名称上来看,就是使用链表实现的,我们来看看它的关键字段
public class LinkedBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {//节点static class Node<E> {//存储元素E item;//下一个节点Node<E> next;//...}//容量上限private final int capacity;//队列元素数量private final AtomicInteger count = new AtomicInteger();//头节点transient Node<E> head;//尾节点private transient Node<E> last;//出队的锁private final ReentrantLock takeLock = new ReentrantLock();//出队的等待队列private final Condition notEmpty = takeLock.newCondition();//入队的锁private final ReentrantLock putLock = new ReentrantLock();//入队的等待队列private final Condition notFull = putLock.newCondition();}
从字段中,我们可以知道它使用单向链表的节点、且用首尾节点记录队列的头尾,并且它使用两把锁、两个等待队列作用于队头、尾,与ArrayBlockingQueue
相比能够增加并发性能
有个奇怪的地方:都使用锁了,为什么记录元素数量count却使用原子类呢?
这是由于两把锁,作用于入队与出队的操作,入队与出队也可能并发执行,同时修改count,因此要使用原子类保证修改数量的原子性
在初始化时需要设置容量大小,否则会设置成无界的阻塞队列(容量是int的最大值)
当消费速度小于生产速度时,阻塞队列中会堆积任务,进而导致容易发生OOM
public LinkedBlockingQueue() {this(Integer.MAX_VALUE);}public LinkedBlockingQueue(int capacity) {if (capacity <= 0) throw new IllegalArgumentException();this.capacity = capacity;last = head = new Node<E>(null);}
来看看入队操作
public boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException {if (e == null) throw new NullPointerException();long nanos = unit.toNanos(timeout);int c = -1;final ReentrantLock putLock = this.putLock;final AtomicInteger count = this.count;//加锁putLock.lockInterruptibly();try {//队列已满,超时返回,不超时等待while (count.get() == capacity) {if (nanos <= 0)return false;nanos = notFull.awaitNanos(nanos);}//入队enqueue(new Node<E>(e));// 先获取再自增 c中存储的是旧值c = count.getAndIncrement();//如果数量没满 唤醒生产者if (c + 1 < capacity)notFull.signal();} finally {//解锁putLock.unlock();}//如果旧值为0 说明该入队操作前是空队列,唤醒消费者来消费if (c == 0)signalNotEmpty();return true;}
入队操作类似,只不过在此期间如果数量没满唤醒生产者生产,队列为空唤醒消费者来消费,从而增加并发性能
入队只是改变指向关系
//添加节点到末尾private void enqueue(Node<E> node) {last = last.next = node;}
唤醒消费者前要先获取锁
private void signalNotEmpty() {final ReentrantLock takeLock = this.takeLock;takeLock.lock();try {notEmpty.signal();} finally {takeLock.unlock();}}
出队操作也类似
public E poll(long timeout, TimeUnit unit) throws InterruptedException {E x = null;int c = -1;long nanos = unit.toNanos(timeout);final AtomicInteger count = this.count;final ReentrantLock takeLock = this.takeLock;takeLock.lockInterruptibly();try {// 队列为空 超时返回空,否则等待while (count.get() == 0) {if (nanos <= 0)return null;nanos = notEmpty.awaitNanos(nanos);}//出队x = dequeue();c = count.getAndDecrement();//队列中除了当前线程获取的任务外还有任务就去唤醒消费者消费if (c > 1)notEmpty.signal();} finally {takeLock.unlock();}//原来队列已满就去唤醒生产者 生产if (c == capacity)signalNotFull();return x;}
LinkedBlockingQueue
与ArrayBlockingQueue
的出队、入队实现类似
只不过LinkedBlockingQueue
入队、出队获取/释放的锁不同,并且在此过程中不同情况回去唤醒其他的生产者、消费者从而进一步提升并发性能
LinkedBlockingQueue 由单向链表实现的阻塞队列,记录首尾节点;默认是无界、非公平的阻塞队列(初始化时要设置容量否则可能OOM),使用两把锁、两个等待队列,分别操作入队、出队的生产者、消费者,在入队、出队操作期间不同情况还会去唤醒生产者、消费者,从而进一步提升并发性能,适用于并发量大的场景
LinkedBlockingDeque
LinkedBlockingDeque
实现与LinkedBlockQueue
类似,在LinkedBlockQueue
的基础上支持从队头、队尾进行添加、删除的操作
它是一个双向链表,带有一系列First、Last的方法,比如:offerLast
、pollFirst
由于LinkedBlockingDeque
双向,常用其来实现工作窃取算法,从而减少线程的竞争
什么是工作窃取算法?
比如多线程处理多个阻塞队列的任务(一一对应),每个线程从队头获取任务处理,当A线程处理完它负责的阻塞队列所有任务时,它再从队尾窃取其他阻塞队列的任务,这样就不会发生竞争,除非队列中只剩一个任务,才会发生竞争
ForkJoin
框架就使用其来充当阻塞队列,我们后文再聊这个框架
PriorityBlockingQueue
PriorityBlockingQueue是优先级排序的无界阻塞队列,阻塞队列按照优先级进行排序
使用堆排序,具体排序算法由Comparable
或Comparator
实现比较规则
- 默认:泛型中的对象需要实现
Comparable
比较规则 ,根据compareTo方法规则排序 - 构造器中指定比较器
Comparator
根据比较器规则排序
@Testpublic void testPriorityBlockingQeque() {//默认使用Integer实现Comparable的升序PriorityBlockingQueue<Integer> queue = new PriorityBlockingQueue<>(6);queue.offer(99);queue.offer(1099);queue.offer(299);queue.offer(992);queue.offer(99288);queue.offer(995);//99 299 992 995 1099 99288while (!queue.isEmpty()){System.out.print(" "+queue.poll());}System.out.println();//指定Comparator 降序queue = new PriorityBlockingQueue<>(6, (o1, o2) -> o2-o1);queue.offer(99);queue.offer(1099);queue.offer(299);queue.offer(992);queue.offer(99288);queue.offer(995);//99288 1099 995 992 299 99while (!queue.isEmpty()){System.out.print(" "+queue.poll());}}
适用于需要根据优先级排序处理的场景
DelayQueue
Delay是一个延时获取元素的无界阻塞队列, 延时最长排在队尾
Delay队列元素实现Delayed接口通过getDelay
获取延时时间
public class DelayQueue<E extends Delayed> extends AbstractQueue<E>implements BlockingQueue<E> {}public interface Delayed extends Comparable<Delayed> {long getDelay(TimeUnit unit);}
DelayQueue应用场景
- 缓存系统的设计:DelayQueue存放缓存有效期,当可以获取到元素时,说明缓存过期
- 定时任务调度: 将定时任务的时间设置为延时时间,一旦可以获取到任务就开始执行
以定时线程池ScheduledThreadPoolExecutor
的定时任务ScheduledFutureTask
为例,它实现Delayed
获取延迟执行的时间
创建对象时,初始化数据
ScheduledFutureTask(Runnable r, V result, long ns, long period) {super(r, result);//time记录当前对象延迟到什么时候可以使用,单位是纳秒this.time = ns;this.period = period;//sequenceNumber记录元素在队列中先后顺序 sequencer原子自增//AtomicLong sequencer = new AtomicLong();this.sequenceNumber = sequencer.getAndIncrement();}
实现Delayed接口的getDelay方法
public long getDelay(TimeUnit unit) {return unit.convert(time - now(), NANOSECONDS);}
Delay接口继承了Comparable接口,目的是要实现compareTo方法来继续排序
public int compareTo(Delayed other) {if (other == this) // compare zero if same objectreturn 0;if (other instanceof ScheduledFutureTask) {ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;long diff = time - x.time;if (diff < 0)return -1;else if (diff > 0)return 1;else if (sequenceNumber < x.sequenceNumber)return -1;elsereturn 1;}long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;}
SynchronousQueue
SynchronousQueue是一个默认下支持非公平不存储元素的阻塞队列
每个put操作要等待一个take操作,否则不能继续添加元素会阻塞
使用公平锁
@Testpublic void testSynchronousQueue() throws InterruptedException {final SynchronousQueue<Integer> queue = new SynchronousQueue(true);new Thread(() -> {try {queue.put(1);queue.put(2);} catch (InterruptedException e) {e.printStackTrace();}}, "put12线程").start();new Thread(() -> {try {queue.put(3);queue.put(4);} catch (InterruptedException e) {e.printStackTrace();}}, "put34线程").start();TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName() + "拿出" + queue.take());TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName() + "拿出" + queue.take());TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName() + "拿出" + queue.take());TimeUnit.SECONDS.sleep(1);System.out.println(Thread.currentThread().getName() + "拿出" + queue.take());}//结果 因为使用公平锁 1在2前,3在4前//main拿出1//main拿出3//main拿出2//main拿出4
SynchronousQueue队列本身不存储元素,负责把生产者的数据传递给消费者,适合传递性的场景
在该场景下吞吐量会比ArrayBlockingQueue,LinkedBlockingQueue高
LinkedTransferQueue
LinkedTransferQueue是一个链表组成的无界阻塞队列,拥有transfer()
和tryTransfer()
方法
transfer()
如果有消费者在等待接收元素,transfer(e)会把元素e传输给消费者
如果没有消费者在等待接收元素,transfer(e)会将元素e存放在队尾,直到有消费者获取了才返回
@Testpublic void testTransfer() throws InterruptedException {LinkedTransferQueue queue = new LinkedTransferQueue();new Thread(()->{try {//阻塞直到被获取queue.transfer(1);//生产者放入的1被取走了System.out.println(Thread.currentThread().getName()+"放入的1被取走了");} catch (InterruptedException e) {e.printStackTrace();}},"生产者").start();TimeUnit.SECONDS.sleep(3);//main取出队列中的元素System.out.println(Thread.currentThread().getName()+"取出队列中的元素");queue.poll();}
tryTransfer()
无论消费者是否消费都直接返回
@Testpublic void testTryTransfer() throws InterruptedException {LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<>();//falseSystem.out.println(queue.tryTransfer(1));//nullSystem.out.println(queue.poll());new Thread(()->{try {//消费者取出2System.out.println(Thread.currentThread().getName()+"取出"+queue.poll(2, TimeUnit.SECONDS));} catch (InterruptedException e) {e.printStackTrace();}},"消费者").start();TimeUnit.SECONDS.sleep(1);//trueSystem.out.println(queue.tryTransfer(2));}
tryTransfer(long,TimeUnit)
在超时时间内消费者消费元素返回true,反之返回false
总结
ArrayBlockingQueue由环形数组实现,固定容量无法扩容,使用非公平的可重入锁锁、两个等待队列操作入队、出队操作,适合并发小的场景
LinkedBlockingQueue由单向链表实现,默认无界,使用两个可重入锁、两个等待队列进行入队、出队操作,并在此期间可能唤醒生产者或消费者线程,以此提高并发性能
LinkedBlockingDeque由双向链表实现,在LinkedBlockingQueue的基础上,能够在队头、队尾都进行添加、删除操作,适用工作窃取算法1
PriorityBlockingQueue由堆排序实现的优先级队列,具体排序算法由Comparable、Comparator来实现,适用于需要根据优先级排序处理任务的场景
DelayQueue 是一个延时队列,队列中存储的元素需要实现Delayed
接口来获取延时时间,适用于缓存失效、定时任务的场景
SynchronousQueue不存储元素,只将生产者生产的元素传递给消费者, 适用于传递性的场景,比如不同线程间传递数据
LinkedTransgerQueue是传输形的阻塞队列,适用于单个元素传递的场景
在使用无界的阻塞队列时,需要设置容量,避免存储任务太多导致OOM
最后(不要白嫖,一键三连求求拉~)
本篇文章被收入专栏 由点到线,由线到面,深入浅出构建Java并发编程知识体系,感兴趣的同学可以持续关注喔
本篇文章笔记以及案例被收入 gitee-StudyJava、 github-StudyJava 感兴趣的同学可以stat下持续关注喔~
案例地址:
Gitee-JavaConcurrentProgramming/src/main/java/E_BlockQueue
Github-JavaConcurrentProgramming/src/main/java/E_BlockQueue
有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~
关注菜菜,分享更多干货,公众号:菜菜的后端私房菜
本文由博客一文多发平台 OpenWrite 发布!
相关文章:

10分钟从实现和使用场景聊聊并发包下的阻塞队列
上篇文章12分钟从Executor自顶向下彻底搞懂线程池中我们聊到线程池,而线程池中包含阻塞队列 这篇文章我们主要聊聊并发包下的阻塞队列 阻塞队列 什么是队列? 队列的实现可以是数组、也可以是链表,可以实现先进先出的顺序队列,…...

Python入门学习13(面向对象)
一、类的定义和使用 类的使用语法: 创建类对象的语法: class Student:name None #学生的名字age None #学生的年龄def say_hi(self):print(f"Hi大家好,我是{self.name}")stu Student() stu.name &q…...

哈工大计算机网络课程网络安全基本原理之:身份认证
哈工大计算机网络课程网络安全基本原理之:身份认证 在日常生活中,在很多场景下我们都需要对当前身份做认证,比如使用密码、人脸识别、指纹识别等,这些都是身份认证的常用方式。本节介绍的身份认证,是在计算机网络安全…...
海外代购系统/代购网站怎么搭建
搭建海外代购系统/代购网站的详细步骤涉及到的内容非常多,本文将分为以下几个部分进行详细介绍:前端开发、后端管理系统的开发、数据库设计和代购流程的设计与实现。 一、前端开发 前端开发是整个代购网站的门面,它直接面向用户,…...
go-micro
go-micro Go Micro简介go-micro体系结构gin-go-micro使用consul实现服务注册与发现实现服务发现批量启动多个服务测试服务发现服务调用在微服务中使用ProtocolBuffergo-micro配置文件...

安装GPU驱动,CUDA Toolkit和配置与CUDA对应的Pytorch
如果有帮助,记得回来点个赞 目录 1.安装指定GPU驱动如果安装的GPU CUDA Version和CUDA Toolkit版本已经冲突怎么办? 2.安装指定版本的CUDA Toolkit如果我安装了CUDA Toolkit之后nvcc -V仍然显示旧的CUDA Toolkit版本怎么办? 3.安装与CUDA对应的Pytorch 1.安装指定GPU驱动 &…...
JavaScript单例模式
JavaScript单例模式 1 什么是单例模式2 实现一个基础的单例模式3 透明的单例模式4 用代理实现单例模式5 JavaScript 中的单例模式6 惰性单例 1 什么是单例模式 保证一个类只有一个实例,并提供一个访问它的全局访问点,这就是单例模式。 单例模式是一种常…...
centos下安装jenkins.war
https://get.jenkins.io/war-stable/ 下载jenkins.war包,(2.164.1 版本支持1.8,其他的都是jdk11),可以安装完成后更新jenkins.war的安装包启动jenkins命令 java -jar jenkins.war --httpPort8010访问http://IP:8010/jenkins (密码在/root/.jenkins/secre…...

App线上网络问题优化策略
在我们App开发过程中,网络是必不可少的,几乎很难想到有哪些app是不需要网络传输的,所以网络问题一般都是线下难以复现,一旦到了用户手里就会碰到很多疑难杂症,所以对于网络的监控是必不可少的,针对用户常见…...

PDF 工具箱
PDF 工具箱 V9.0.0.1 程序:VB.net 运行库:NET Framework 4.5 功能简介: 1、PDF文件多文件合并,可调整顺序。 2、PDF文件拆分,将每页拆分成独立的PDF文件。 3、PDF文件添加水印,文字或图片水印&…...
大数据组件系列-Hadoop每日小问
1、谈谈对HDFS的理解?HDFS这种存储适合哪些场景? HDFS即Hadoop Distributed File System,Hadoop 分布式文件系统。它为的是解决海量数据的存储与分析的问题,它本身是源于Google在大数据方面的论文,GFS-->HDFS; HD…...

【前端】在Vue页面中引入其它vue页面 数据传输 相互调用方法等
主页面 home 从页面 headView 需求 在 home.vue 中引用 headView.Vue 方案: home.vue 代码: 只需要在home.vue 想要的地方添加 <headView></headView> <script>//聊天页面 import headView /view/headView.vueexport default {components: {headView},…...

网络通信深入解析:探索TCP/IP模型
http协议访问web 你知道在我们的网页浏览器的地址当中输入url,未必是如何呈现的吗? web浏览器根据地址栏中指定的url,从web服务器获取文件资源(resource)等信息,从而显示出web页面。web使用HTTP(…...

可靠的可视化监控平台应用在那些场景?
可视化监控平台是一种用户友好的工具,可以帮助用户实时监控IT设备的运行状态和网络流量,以及监测安全性和性能指标。它们通常采用图形化界面,使得用户能够直观地了解设备和网络的状态。 以下是一些可视化监控平台常见的应用场景:…...

从 BBR 失速到带宽探测
看一下 pacing 流失速的成因: 一段时间收不到 ack,丢了 ack 自时钟,cwnd 将耗尽,bbr 虽有 cwnd_gain(上图没有表现),但在该 cwnd_gain 下不依赖 ack 持续坚持发送多久取决于 cwnd_gain 的数值。 bbr 失速的后果在于…...
MobaXterm使用sz/rz命令下载上传文件
MobaXterm使用sz/rz命令下载上传文件 1 参考文档2 下载3 上传 1 参考文档 MobaXterm使用sz/rz命令下载上传文件 2 下载 步骤1:sz filename 步骤2:ctrl 鼠标右键 步骤3:Receive file using Z-modem 3 上传 步骤1:rz 步骤2&am…...
vue el-popover hover延时触发,el-popover 鼠标放上三秒以后触发
背景:el-popover hover只要鼠标刮过就显示 多个el-popover出现加载卡顿 解决方案 给el-popover加一个延时显示 <template><div><el-popovertrigger"hover":open-delay"3000"content"这是一个Popover"><button…...

计算机竞赛 基于深度学习的人脸识别系统
前言 🔥 优质竞赛项目系列,今天要分享的是 基于深度学习的人脸识别系统 该项目较为新颖,适合作为竞赛课题方向,学长非常推荐! 🧿 更多资料, 项目分享: https://gitee.com/dancheng-senior/…...

Android扫码连接WIFI实现
0,目标 APP中实现扫WIFI分享码自动连接WIFI功能 1,前提条件 设备需要有个扫码器(摄像头拍照识别也行),APP调用扫码器读取WIFI连接分享码。 2,增加权限 在AndroidManifest.xml中增加权限 <uses-permissi…...

TrOCR – 基于 Transformer 的 OCR 入门指南
多年来,光学字符识别 (OCR) 出现了多项创新。它对零售、医疗保健、银行和许多其他行业的影响是巨大的。尽管有着悠久的历史和多种最先进的模型,研究人员仍在不断创新。与深度学习的许多其他领域一样,OCR 也看到了变压器神经网络的重要性和影响。如今,我们拥有像TrOCR(Tran…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...

tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
前端高频面试题2:浏览器/计算机网络
本专栏相关链接 前端高频面试题1:HTML/CSS 前端高频面试题2:浏览器/计算机网络 前端高频面试题3:JavaScript 1.什么是强缓存、协商缓存? 强缓存: 当浏览器请求资源时,首先检查本地缓存是否命中。如果命…...
命令行关闭Windows防火墙
命令行关闭Windows防火墙 引言一、防火墙:被低估的"智能安检员"二、优先尝试!90%问题无需关闭防火墙方案1:程序白名单(解决软件误拦截)方案2:开放特定端口(解决网游/开发端口不通)三、命令行极速关闭方案方法一:PowerShell(推荐Win10/11)方法二:CMD命令…...

高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
2024 年,高端封装市场规模为 80 亿美元,预计到 2030 年将超过 280 亿美元,2024-2030 年复合年增长率为 23%。 细分到各个终端市场,最大的高端性能封装市场是“电信和基础设施”,2024 年该市场创造了超过 67% 的收入。…...