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

10分钟从实现和使用场景聊聊并发包下的阻塞队列

上篇文章12分钟从Executor自顶向下彻底搞懂线程池中我们聊到线程池,而线程池中包含阻塞队列

这篇文章我们主要聊聊并发包下的阻塞队列

阻塞队列

什么是队列?

队列的实现可以是数组、也可以是链表,可以实现先进先出的顺序队列,也可以实现先进后出的栈队列

那什么是阻塞队列?

在经典的生产者/消费者模型中,生产者们将生产的元素放入队列,而消费者们从队列获取元素消费

当队列已满,我们会手动阻塞生产者,直到消费者消费再来手动唤醒生产者

当队列为空,我们会手动阻塞消费者,直到生产者生产再来手动唤醒消费者

在这个过程中由于使用的是普通队列,阻塞与唤醒我们需要手动操作,保证同步机制

阻塞队列在队列的基础上提供等待/通知功能,用于线程间的通信,避免线程竞争死锁

生产者可以看成往线程池添加任务的用户线程,而消费者则是线程池中的工作线程

当阻塞队列为空时阻塞工作线程获取任务,当阻塞队列已满时阻塞用户线程向队列中添加任务(创建非核心线程、拒绝策略)

API

阻塞队列提供一下四种添加、删除元素的API,我们常用阻塞等待/超时阻塞等待的API

方法名抛出异常返回true/false阻塞等待超时阻塞等待
添加add(Object)offer(Object)put(Object)offer(Object,long,TimeUnit)
删除remove()poll()take()poll(long,TimeUnit)
  1. 抛出异常:队满add 抛出异常IllegalStateExceptio ;队空remove 抛出异常NoSuchElementException
  2. 返回值: 队满offer返回false,队空poll返回null
  3. 阻塞等待: 队满时put会阻塞线程 或 队空时take会阻塞线程
  4. 超时阻塞等待: 在阻塞等待、返回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;}

LinkedBlockingQueueArrayBlockingQueue的出队、入队实现类似

只不过LinkedBlockingQueue入队、出队获取/释放的锁不同,并且在此过程中不同情况回去唤醒其他的生产者、消费者从而进一步提升并发性能

LinkedBlockingQueue 由单向链表实现的阻塞队列,记录首尾节点;默认是无界、非公平的阻塞队列(初始化时要设置容量否则可能OOM),使用两把锁、两个等待队列,分别操作入队、出队的生产者、消费者,在入队、出队操作期间不同情况还会去唤醒生产者、消费者,从而进一步提升并发性能,适用于并发量大的场景

LinkedBlockingDeque

LinkedBlockingDeque实现与LinkedBlockQueue类似,在LinkedBlockQueue的基础上支持从队头、队尾进行添加、删除的操作

它是一个双向链表,带有一系列First、Last的方法,比如:offerLastpollFirst

由于LinkedBlockingDeque双向,常用其来实现工作窃取算法,从而减少线程的竞争

什么是工作窃取算法?

比如多线程处理多个阻塞队列的任务(一一对应),每个线程从队头获取任务处理,当A线程处理完它负责的阻塞队列所有任务时,它再从队尾窃取其他阻塞队列的任务,这样就不会发生竞争,除非队列中只剩一个任务,才会发生竞争

ForkJoin框架就使用其来充当阻塞队列,我们后文再聊这个框架

PriorityBlockingQueue

PriorityBlockingQueue是优先级排序的无界阻塞队列,阻塞队列按照优先级进行排序

使用堆排序,具体排序算法由ComparableComparator实现比较规则

  1. 默认:泛型中的对象需要实现Comparable比较规则 ,根据compareTo方法规则排序
  2. 构造器中指定比较器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应用场景

  1. 缓存系统的设计:DelayQueue存放缓存有效期,当可以获取到元素时,说明缓存过期
  2. 定时任务调度: 将定时任务的时间设置为延时时间,一旦可以获取到任务就开始执行

以定时线程池ScheduledThreadPoolExecutor的定时任务ScheduledFutureTask为例,它实现Delayed获取延迟执行的时间

image.png

  1. 创建对象时,初始化数据

             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();}
  2. 实现Delayed接口的getDelay方法

     public long getDelay(TimeUnit unit) {return unit.convert(time - now(), NANOSECONDS);}
  3. 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自顶向下彻底搞懂线程池中我们聊到线程池&#xff0c;而线程池中包含阻塞队列 这篇文章我们主要聊聊并发包下的阻塞队列 阻塞队列 什么是队列&#xff1f; 队列的实现可以是数组、也可以是链表&#xff0c;可以实现先进先出的顺序队列&#xff0c;…...

Python入门学习13(面向对象)

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

哈工大计算机网络课程网络安全基本原理之:身份认证

哈工大计算机网络课程网络安全基本原理之&#xff1a;身份认证 在日常生活中&#xff0c;在很多场景下我们都需要对当前身份做认证&#xff0c;比如使用密码、人脸识别、指纹识别等&#xff0c;这些都是身份认证的常用方式。本节介绍的身份认证&#xff0c;是在计算机网络安全…...

海外代购系统/代购网站怎么搭建

搭建海外代购系统/代购网站的详细步骤涉及到的内容非常多&#xff0c;本文将分为以下几个部分进行详细介绍&#xff1a;前端开发、后端管理系统的开发、数据库设计和代购流程的设计与实现。 一、前端开发 前端开发是整个代购网站的门面&#xff0c;它直接面向用户&#xff0c…...

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 什么是单例模式 保证一个类只有一个实例&#xff0c;并提供一个访问它的全局访问点&#xff0c;这就是单例模式。 单例模式是一种常…...

centos下安装jenkins.war

https://get.jenkins.io/war-stable/ 下载jenkins.war包,(2.164.1 版本支持1.8&#xff0c;其他的都是jdk11),可以安装完成后更新jenkins.war的安装包启动jenkins命令 java -jar jenkins.war --httpPort8010访问http://IP:8010/jenkins &#xff08;密码在/root/.jenkins/secre…...

App线上网络问题优化策略

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

PDF 工具箱

PDF 工具箱 V9.0.0.1 程序&#xff1a;VB.net 运行库&#xff1a;NET Framework 4.5 功能简介&#xff1a; 1、PDF文件多文件合并&#xff0c;可调整顺序。 2、PDF文件拆分&#xff0c;将每页拆分成独立的PDF文件。 3、PDF文件添加水印&#xff0c;文字或图片水印&…...

大数据组件系列-Hadoop每日小问

1、谈谈对HDFS的理解&#xff1f;HDFS这种存储适合哪些场景&#xff1f; HDFS即Hadoop Distributed File System&#xff0c;Hadoop 分布式文件系统。它为的是解决海量数据的存储与分析的问题&#xff0c;它本身是源于Google在大数据方面的论文&#xff0c;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&#xff0c;未必是如何呈现的吗&#xff1f; web浏览器根据地址栏中指定的url&#xff0c;从web服务器获取文件资源&#xff08;resource&#xff09;等信息&#xff0c;从而显示出web页面。web使用HTTP&#xff08…...

可靠的可视化监控平台应用在那些场景?

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

从 BBR 失速到带宽探测

看一下 pacing 流失速的成因&#xff1a; 一段时间收不到 ack&#xff0c;丢了 ack 自时钟&#xff0c;cwnd 将耗尽&#xff0c;bbr 虽有 cwnd_gain(上图没有表现)&#xff0c;但在该 cwnd_gain 下不依赖 ack 持续坚持发送多久取决于 cwnd_gain 的数值。 bbr 失速的后果在于…...

MobaXterm使用sz/rz命令下载上传文件

MobaXterm使用sz/rz命令下载上传文件 1 参考文档2 下载3 上传 1 参考文档 MobaXterm使用sz/rz命令下载上传文件 2 下载 步骤1&#xff1a;sz filename 步骤2&#xff1a;ctrl 鼠标右键 步骤3&#xff1a;Receive file using Z-modem 3 上传 步骤1&#xff1a;rz 步骤2&am…...

vue el-popover hover延时触发,el-popover 鼠标放上三秒以后触发

背景&#xff1a;el-popover hover只要鼠标刮过就显示 多个el-popover出现加载卡顿 解决方案 给el-popover加一个延时显示 <template><div><el-popovertrigger"hover":open-delay"3000"content"这是一个Popover"><button…...

计算机竞赛 基于深度学习的人脸识别系统

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

Android扫码连接WIFI实现

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

TrOCR – 基于 Transformer 的 OCR 入门指南

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

C++实现分布式网络通信框架RPC(3)--rpc调用端

目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中&#xff0c;我们已经大致实现了rpc服务端的各项功能代…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

前端倒计时误差!

提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

使用分级同态加密防御梯度泄漏

抽象 联邦学习 &#xff08;FL&#xff09; 支持跨分布式客户端进行协作模型训练&#xff0c;而无需共享原始数据&#xff0c;这使其成为在互联和自动驾驶汽车 &#xff08;CAV&#xff09; 等领域保护隐私的机器学习的一种很有前途的方法。然而&#xff0c;最近的研究表明&…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

JUC笔记(上)-复习 涉及死锁 volatile synchronized CAS 原子操作

一、上下文切换 即使单核CPU也可以进行多线程执行代码&#xff0c;CPU会给每个线程分配CPU时间片来实现这个机制。时间片非常短&#xff0c;所以CPU会不断地切换线程执行&#xff0c;从而让我们感觉多个线程是同时执行的。时间片一般是十几毫秒(ms)。通过时间片分配算法执行。…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解

在 C/C 编程的编译和链接过程中&#xff0c;附加包含目录、附加库目录和附加依赖项是三个至关重要的设置&#xff0c;它们相互配合&#xff0c;确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中&#xff0c;这些概念容易让人混淆&#xff0c;但深入理解它们的作用和联…...

日常一水C

多态 言简意赅&#xff1a;就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过&#xff0c;当子类和父类的函数名相同时&#xff0c;会隐藏父类的同名函数转而调用子类的同名函数&#xff0c;如果要调用父类的同名函数&#xff0c;那么就需要对父类进行引用&#…...