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

news2024/11/25 11:31:00

上篇文章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 根据比较器规则排序
     @Test
     public 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 99288
         while (!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 99
         while (!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 object
                     return 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;
                     else
                         return 1;
                 }
                 long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
                 return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
             }

SynchronousQueue

SynchronousQueue是一个默认下支持非公平不存储元素的阻塞队列

每个put操作要等待一个take操作,否则不能继续添加元素会阻塞

使用公平锁

     @Test
     public 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存放在队尾,直到有消费者获取了才返回

     @Test
     public 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()无论消费者是否消费都直接返回

     @Test
     public void testTryTransfer() throws InterruptedException {
         LinkedTransferQueue<Integer> queue = new LinkedTransferQueue<>();
         //false
         System.out.println(queue.tryTransfer(1));
         //null
         System.out.println(queue.poll());
 ​
         new Thread(()->{
             try {
                 //消费者取出2
                 System.out.println(Thread.currentThread().getName()+"取出"+queue.poll(2, TimeUnit.SECONDS));
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         },"消费者").start();
         TimeUnit.SECONDS.sleep(1);
         //true
         System.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 发布!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【矩阵分解】PCA - 主成分分析中的数学原理

前言 本文主要对PCA主成分分析中的数学原理进行介绍&#xff0c;将不涉及或很少涉及代码实现或应用&#xff0c;阅读前请确保已了解基本的机器学习相关知识。 文章概述 PCA主成分分析属于矩阵分解算法中的入门算法&#xff0c;通过分解特征矩阵来实现降维。 本文主要内容&a…

【PowerQuery】Excel 一分钟以内刷新PowerQuery数据

当需要进行刷新的周期如果小于一分钟,采用数据自动刷新就无法实现自动刷新的目标。那就没有办法了吗?当然不是,这里就是使用VBA来实现自动刷新。这里实现VBA刷新的第一步就是将当前的Excel 保存为带有宏的Excel 文件,如果不带宏则无法运行带有宏代码的Excel文件,保存过程如…

JAVA中的String类中的一些常用方法

目录 字符串比较方法&#xff1a; boolean equals(Object anObject)&#xff1a; int compareTo(String s)&#xff1a; int compareToIgnoreCase(String str) 字符串查找方法&#xff1a; char charAt(int index)&#xff1a; int indexOf(int ch)&#xff1a; int inde…

sqlserver2012性能优化配置:设置性能相关的服务器参数

前言 sqlserver2012 长时间运行的话会将服务器的内存占满 解决办法 通过界面设置 下图中设置最大服务器内存 通过执行脚本设置 需要先开发开启高级选项配置才能设置成功 设置完成之后将高级选择配置关闭&#xff0c;还原成跟之前一样 --可以配置高级选项 EXEC sp_conf…

MySQL--数据库基础

数据库分类 数据库大体可以分为 关系型数据库 和 非关系型数据库 常用数据类型 数值类型&#xff1a; 分为整型和浮点型&#xff1a; 字符串类型 日期类型

【SpringMVC】一行代码完成文件上传JRebel的使用

目录 引言 一、JRebel的使用 1.1.安装JReble 1.2.反向代理工具 1.3.离线使用 二、文件上传 2.1.公共文件跳转 2.2.添加依赖 2.3.配置文件上传解析器 2.4.图片路径配置Tomcat 2.5.前端代码 2.6.文件上传实现 三、文件下载 3.1.Controller层 3.2.前端代码 四、多文…

Jetsonnano B01 笔记4:UART 通信配置及编程

今日继续我的Jetsonnano学习之路&#xff0c;今日学习使用Jetson硬件驱动之UART串口通信&#xff1a; 目录 简议串口通信&#xff1a; 硬件连接&#xff1a; 串口配置&#xff1a; 安装串口函数库&#xff1a; 设置权限&#xff1a; Python代码配置&#xff1a; 下载测试…

机器学习实战-系列教程5:手撕线性回归4之非线性回归(项目实战、原理解读、源码解读)

11、非线性模型 当得到一个回归方程会&#xff0c;得到一条直线来拟合这个数据的统计规律&#xff0c;但是实际中用这样的简单直线很显然并不能拟合出统计规律&#xff0c;所谓线性回归比如两个变量之间关系就直接用一条直线来拟合&#xff0c;2个变量和一个1个变量的关系就用…

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;文字或图片水印&…

代码随想录 -- day46 --139.单词拆分

139.单词拆分 dp[i] : 字符串长度为i的话&#xff0c;dp[i]为true&#xff0c;表示可以拆分为一个或多个在字典中出现的单词 递推公式是 if([j, i] 这个区间的子串出现在字典里 && dp[j]是true) 那么 dp[i] true。 本题一定是 先遍历 背包&#xff0c;再遍历物品 c…

【LeetCode题目详解】第九章 动态规划part09 198.打家劫舍 213.打家劫舍II 337.打家劫舍III(day48补)

本文章代码以c为例&#xff01; 一、力扣第198题&#xff1a;打家劫舍 题目&#xff1a; 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻…

【再识C进阶2(中)】详细介绍指针的进阶——函数指针数组、回调函数、qsort函数

前言 &#x1f493;作者简介&#xff1a; 加油&#xff0c;旭杏&#xff0c;目前大二&#xff0c;正在学习C&#xff0c;数据结构等&#x1f440; &#x1f493;作者主页&#xff1a;加油&#xff0c;旭杏的主页&#x1f440; ⏩本文收录在&#xff1a;再识C进阶的专栏&#x1…

开开心心带你学习MySQL数据库之第七篇

MySQL提供的约束 1.not null 2.unique 3.default 4.primary key 5.foreign key 表的设计 找到实体确定实体间的关系 一对一一对多多对多 聚合查询 ~~行之间的运算 ~~聚合函数 ~~分组group by 联合查询 ~~多表查询 ~~笛卡尔积: 把两个表放到一起进行排列组合 班级表 cla…

代码随想录 -- day45 -- 70. 爬楼梯 (进阶)、322. 零钱兑换 、279.完全平方数

70. 爬楼梯 &#xff08;进阶&#xff09; 这里要注意&#xff0c;这是一个排列组合的问题&#xff0c;所以要先遍历背包再遍历物品 dp[i]&#xff1a;爬到有i个台阶的楼顶&#xff0c;有dp[i]种方法 递推公式为&#xff1a;dp[i] dp[i - j] class Solution { public:int c…

基于51单片机万年历电压电流检测-proteus仿真-源程序

一、系统方案 本设计采用52单片机作为主控器&#xff0c;液晶1602显示&#xff0c;DS1302时钟检测&#xff0c;电流电压检测、按键设置报警&#xff0c;蜂鸣器报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /lcd1602初始化设置*/ vo…

Java中什么是序列化,哪里有所应用

文章目录 一、简介1.1 本文介绍Java中的序列化技术1.2 阐述序列化的应用场景 二、Java序列化概述2.1 序列化定义2.2 序列化特征2.3 序列化机制 三、Java序列化使用3.1 实现Serializable接口3.2 transient关键字3.3 自定义序列化策略 四、Java序列化应用4.1 对象状态持久化4.2 网…

RCP系列-第一章 环境安装

RCP系列文章 第一章 Matlab安装 Matlab安装 RCP系列文章前言一、Matlab 获取二、安装1.解压2.打开解压后的文件夹中的【R2018b_win64】文件夹3.鼠标右击【setup】选择【以管理员身份运行】4.选择【使用文件安装密钥】&#xff0c;点击【下一步】5.选择【是】&#xff0c;点击【…

图像处理算法实战【1】超详细整理 | 新手入门实用指南 | 图像处理基础

1. 什么是图像 & 图像在计算机中如何存储&#xff1f;2. 图像可分为哪些类型&#xff1f; 2.1. 二值(黑白)图像2.2. 灰度图像2.3. RGB彩色图像2.4. RGBA图像 3. 什么是图像通道&#xff1f;4. 图像处理 4.1. 什么是图像处理&#xff1f;4.2. 图像处理流程4.3. 图像处理技术…

王道考研计算机网络

文章目录 计算机网络体系结构计算机网络概述计算机网络的性能指标 计算机网络体系结构与参考模型错题 物理层通信基础基础概念奈奎斯特定理和香农定理编码与调制电路交换、报文交换和分组交换数据报与虚电路 传输介质物理层设备错题 数据链路层数据链路层的功能组帧差错控制检错…

SpringSecurity一日干

前后端登录校验的逻辑 完整流程 本质就是过滤器链 1&#xff0c;提交用户名和密码 2&#xff0c;将提交的信息封装Authentication对象 3&#xff0c;传给下一个&#xff0c;调用2中的authenticate方法进行验证 4&#xff0c;3步骤也验证不了需要调用3的authenticate方法…