Java 并发编程面试题——BlockingQueue

目录

  • 1.什么是阻塞队列 (BlockingQueue)?
  • 2.BlockingQueue 有哪些核心方法?
  • 3.BlockingQueue 有哪些常用的实现类?
    • 3.1.ArrayBlockingQueue
    • 3.2.DelayQueue
    • 3.3.LinkedBlockingQueue
    • 3.4.PriorityBlockingQueue
    • 3.5.SynchronousQueue
  • 4.✨BlockingQueue 的实现原理是什么?
    • 4.1.构造器
    • 4.2.put 操作
    • 4.3.take 操作
  • 5.BlockingQueue 的使用场景有哪些?
    • 5.1.生产者—消费者模式
    • 5.2.线程池中使用阻塞队列
  • 6.✨手动实现一个简单的阻塞队列?

1.什么是阻塞队列 (BlockingQueue)?

BlockingQueuejava.util.concurrent 包下重要的数据结构,与普通的队列不同,BlockingQueue 提供了线程安全的队列访问方式,该包下很多高级同步类的实现都是基于 BlockingQueue 实现的。BlockingQueue ⼀般用于生产者——消费者模式,生产者是往队列里添加元素的线程, 消费者是从队列里取出元素的线程。BlockingQueue 就是存放元素的容器

2.BlockingQueue 有哪些核心方法?

(1)BlockingQueue 是一个接口,继承自 Queue,所以其实现类也可以作为 Queue 的实现来使用,而 Queue 又继承自 Collection 接口。

在这里插入图片描述

(2)BlockingQueue 中的核心方法如下:

方法 \ 处理方式抛出异常返回特殊值一直阻塞超时退出
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
检查element()peek()--
  • 抛出异常:如果试图的操作无法立即执行,抛出异常。当阻塞队列满时候,再往队列里插入元素,会抛出 IllegalStateException(“Queue full”) 异常。当队列为空时,从队列里获取元素时会抛出 NoSuchElementException 异常。
  • 返回特殊值:如果试图的操作无法立即执行,返回⼀个特殊值,通常是 true / false。
  • ⼀直阻塞:如果试图的操作无法立即执行,则⼀直阻塞或者响应中断。
  • 超时退出:如果试图的操作无法立即执行,该⽅法调⽤将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回⼀个特定值以告知该操作是否成功,通常是 true / false。

注意:
① 不能往阻塞队列中插入 null,否则会抛出空指针异常。
② 可以访问阻塞队列中的任意元素,调用 remove(o) 可以将队列之中的特定对象移除,但并不高效,尽量避免使用。

(3)BlockingQueue 的源码如下:

package java.util.concurrent;import java.util.Collection;
import java.util.Queue;public interface BlockingQueue<E> extends Queue<E> {boolean add(E e);boolean offer(E e);void put(E e) throws InterruptedException;boolean offer(E e, long timeout, TimeUnit unit)throws InterruptedException;E take() throws InterruptedException;E poll(long timeout, TimeUnit unit)throws InterruptedException;int remainingCapacity();boolean remove(Object o);public boolean contains(Object o);int drainTo(Collection<? super E> c);int drainTo(Collection<? super E> c, int maxElements);
}

3.BlockingQueue 有哪些常用的实现类?

下面主要介绍 5 个常用的 BlockingQueue 实现类。

在这里插入图片描述

3.1.ArrayBlockingQueue

(1)ArrayBlockingQueue 是由数组结构组成的有界阻塞队列。内部结构是数组,故具有数组的特性。其源码中的构造函数如下:

public class ArrayBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {//...public ArrayBlockingQueue(int capacity) {this(capacity, false);}public ArrayBlockingQueue(int capacity, boolean fair) {if (capacity <= 0)throw new IllegalArgumentException();//...}public ArrayBlockingQueue(int capacity, boolean fair,Collection<? extends E> c) {this(capacity, fair);//...}
}

(2)ArrayBlockingQueue 一旦创建,容量不能改变。其并发控制采用可重入锁 ReentrantLock,不管是插入操作还是读取操作,都需要获取到锁才能进行操作。当队列容量满时,尝试将元素放入队列将导致操作阻塞;尝试从一个空队列中取一个元素也会同样阻塞。

(3)ArrayBlockingQueue 默认情况下不能保证线程访问队列的公平性,所谓公平性是指严格按照线程等待的绝对时间顺序,即最先等待的线程能够最先访问到 ArrayBlockingQueue。而非公平性则是指访问 ArrayBlockingQueue 的顺序不是遵守严格的时间顺序,有可能存在当 ArrayBlockingQueue 可以被访问时,长时间阻塞的线程依然无法访问到 ArrayBlockingQueue 的情况。如果保证公平性,那么通常会降低吞吐量。如果需要获得公平性的 ArrayBlockingQueue,可采用如下代码:

ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10, true);

3.2.DelayQueue

(1)DelayQueue 中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue 的应用场景主要是处理具有延迟需求的任务调度,比如定时任务、缓存过期等等。它提供了一种方便的方式来实现元素的延迟处理。。源码中的构造方法如下所示:

public class DelayQueue<E extends Delayed> extends AbstractQueue<E>implements BlockingQueue<E> {//...	public DelayQueue() {}public DelayQueue(Collection<? extends E> c) {this.addAll(c);}
}

(2)DelayQueue 的特点如下:

  • 元素按照延迟时间的顺序进行排序,延迟时间越短的元素排在队列的前面。
  • 元素只有在指定的延迟时间到达后才可以从队列中取出。
  • 如果队列中没有到达延迟时间的元素,那么从队列中取元素的操作将会被阻塞,直到有元素到达延迟时间。
  • DelayQueue 是线程安全的,多个线程可以安全地操作同一个 DelayQueue 实例。

(3)DelayQueue 的底层实现原理是基于 PriorityQueue(优先级队列)和 ReentrantLock(可重入锁)实现的。

  • DelayQueue 使用 PriorityQueue 作为其内部数据结构,这是一个基于堆的优先级队列,用于存储元素并按照其延迟时间进行排序。在 PriorityQueue 中,延迟时间越短的元素排在队列的前面。
  • 当元素被插入到 DelayQueue 中时,会根据元素的延迟时间进行排序,并被放置在对应的位置。取出元素时,会检查队列头部的元素是否已经到达了延迟时间,如果还未到达延迟时间,则会阻塞等待。当元素的延迟时间到达后,该元素可以被取出。
  • 为了保证线程安全性和可并发性,DelayQueue 使用了 ReentrantLock 进行同步,确保多个线程可以安全地操作 DelayQueue。
  • 另外,DelayQueue 中的元素需要实现 Delayed 接口,这个接口定义了两个方法:getDelay(TimeUnit unit)compareTo(Delayed o)。前者用于获取当前元素的剩余延迟时间,后者用于比较两个元素的延迟时间大小。这样,DelayQueue就能根据元素的延迟时间进行有序排列。

总结起来,DelayQueue 的底层实现原理是基于 PriorityQueue 和 ReentrantLock。PriorityQueue 用于按照延迟时间对元素进行排序,ReentrantLock 用于保证线程安全性。通过这两者的组合,DelayQueue 可以有效地实现具有延迟需求的任务调度功能。

(4)使用 DelayQueue 的示例如下:

class DelayedElement implements Delayed {private String element;private long delayTime;public DelayedElement(String element, long delayTime) {this.element = element;this.delayTime = System.currentTimeMillis() + delayTime;}@Overridepublic long getDelay(TimeUnit unit) {long remainingTime = delayTime - System.currentTimeMillis();return unit.convert(remainingTime, TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed other) {long diff = getDelay(TimeUnit.MILLISECONDS) - other.getDelay(TimeUnit.MILLISECONDS);return Long.compare(diff, 0);}public String getElement() {return element;}
}class DelayQueueExample {public static void main(String[] args) throws InterruptedException {DelayQueue<DelayedElement> delayQueue = new DelayQueue<>();//添加延时元素到队列中delayQueue.add(new DelayedElement("Element 1", 2000));delayQueue.add(new DelayedElement("Element 2", 500));delayQueue.add(new DelayedElement("Element 3", 3000));//从队列中取出延时元素while (!delayQueue.isEmpty()) {DelayedElement element = delayQueue.take();System.out.println("取出元素:" + element.getElement());}}
}

3.3.LinkedBlockingQueue

(1)LinkedBlockingQueue 底层基于单向链表实现的阻塞队列,可以当做无界队列也可以当做有界队列来使用,同样满足 FIFO 的特性,与 ArrayBlockingQueue 相比起来具有更高的吞吐量,为了防止 LinkedBlockingQueue 容量迅速增,损耗大量内存。通常在创建 LinkedBlockingQueue 对象时,会指定其大小,如果未指定,容量等于 Integer.MAX_VALUE

(2)源码中的构造方法如下所示:

public class LinkedBlockingDeque<E>extends AbstractQueue<E>implements BlockingDeque<E>, java.io.Serializable {//...public LinkedBlockingDeque() {this(Integer.MAX_VALUE);}public LinkedBlockingDeque(int capacity) {if (capacity <= 0) throw new IllegalArgumentException();this.capacity = capacity;}public LinkedBlockingDeque(Collection<? extends E> c) {this(Integer.MAX_VALUE);//...}
}

3.4.PriorityBlockingQueue

(1)PriorityBlockingQueue 是一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序,也可以通过自定义类实现 compareTo() 方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator 来指定排序规则。

(2)PriorityBlockingQueue 并发控制采用的是可重入锁 ReentrantLock,队列为无界队列(ArrayBlockingQueue 是有界队列,LinkedBlockingQueue 也可以通过在构造函数中传入 capacity 指定队列最大的容量,但是 PriorityBlockingQueue 只能指定初始的队列大小,后面插入元素的时候,如果空间不够的话会自动扩容)。简单地说,它就是 PriorityQueue 的线程安全版本。不可以插入 null 值,同时,插入队列的对象必须是可比较大小的 (comparable),否则报 ClassCastException 异常。它的插入操作 put 方法不会 block,因为它是无界队列(take 方法在队列为空的时候会阻塞)。

(3)源码中的构造方法如下所示:

public class PriorityBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {//...public PriorityBlockingQueue() {this(DEFAULT_INITIAL_CAPACITY, null);}public PriorityBlockingQueue(int initialCapacity) {this(initialCapacity, null);}	public PriorityBlockingQueue(int initialCapacity,Comparator<? super E> comparator) {//...}public PriorityBlockingQueue(Collection<? extends E> c) {//...}
}

3.5.SynchronousQueue

SynchronousQueue 这个队列比较特殊,没有任何内部容量,是一种无缓冲的等待队列,类似于无中介的直接交易。并且每个 put 必须等待⼀个 take,反之亦然。 需要区别容量为 1 的 ArrayBlockingQueue、LinkedBlockingQueue。以下方法的返回值,可以帮助理解这个队列:

方法返回值
iterator()永远返回 null
peek()永远返回 null
put()往队列里放进去⼀个元素以后就⼀直等待,直到有其他线程将该元素取走
offer()往队列里放⼀个元素后立即返回,如果碰巧该元素被另⼀个线程取走了,那么该方法返回 true,认为 offer 成功;否则返回 false
take()取出队列中的元素,若取不到则一直等待
poll()只有到碰巧另外⼀个线程正在往队列中 offer 元素或者 put 元素时,该方法才会取到元素;否则立即返回 null
isEmpty()永远返回 true
remove() & removeAll()永远返回 false

4.✨BlockingQueue 的实现原理是什么?

阻塞队列的原理很简单,利⽤了 Lock 锁的多条件 (Condition) 阻塞控制。下面对 ArrayBlockingQueue JDK 1.8 的源码进行分析。

4.1.构造器

首先是构造器,除了初始化队列的大小和是否是公平锁之外,还对同⼀个锁 (lock) 初始化了两个监视器,分别是 notEmptynotFull。这两个监视器的作用目前可以简单理解为标记分组:

  • 当该线程是 put 操作时,给他加上监视器 notFull,标记这个线程是⼀个生产者
  • 当线程是 take 操作时,给他加上监视器 notEmpty,标记这个线程是⼀个消费者
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; public ArrayBlockingQueue(int capacity, boolean fair) {//..省略其他代码lock = new ReentrantLock(fair);notEmpty = lock.newCondition();notFull = lock.newCondition();}//...
}

4.2.put 操作

put 的流程如下:

  • 所有执行 put 操作的线程竞争 lock 锁,拿到了 lock 锁的线程进入下⼀步,没有拿到 lock 锁的线程自旋竞争锁
  • 判断阻塞队列是否满了:
    • 如果满了,则调用 notFull.await() 方法阻塞这个线程,并标记为 notFull(生产者)线程,同时释放 lock 锁,等待被消费者线程唤醒。
    • 如果没有满,则调用 enqueue 方法将元素 put 进阻塞队列。注意这⼀步的线程还有⼀种情况是第⼆步中阻塞的线程被唤醒且又拿到了lock 锁的线程。
  • 释放 lock 锁,并唤醒⼀个标记为 notEmpty(消费者)的线程。
public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;//⾃旋拿锁lock.lockInterruptibly();try {//判断队列是否满了while (count == items.length)//如果满了,阻塞该线程,并标记为 notFull 线程,等待 notEmpty 的唤醒,唤醒之后继续执⾏ while 循环notFull.await();//如果没有满,则进⼊队列enqueue(e);} finally {lock.unlock();}
}private void enqueue(E x) {// assert lock.getHoldCount() == 1;// assert items[putIndex] == null;final Object[] items = this.items;items[putIndex] = x;if (++putIndex == items.length)putIndex = 0;count++;//唤醒⼀个等待的线程notEmpty.signal();
}

4.3.take 操作

take 操作和 put 操作的流程是类似的,总结⼀下 take 操作的流程:

  • 所有执行 take 操作的线程竞争 lock 锁,拿到了 lock 锁的线程进入下⼀步,没有拿到 lock 锁的线程自旋竞争锁。
  • 判断阻塞队列是否为空:
    • 如果是空,则调用 notEmpty.await 方法阻塞这个线程,并标记为 notEmpty(消费者)线程,同时释放 lock 锁,等待被生产者线程唤醒。
    • 如果没有空,则调用 dequeue 方法。注意这⼀步的线程还有⼀种情况是第⼆步中阻塞的线程被唤醒且又拿到了 lock 锁的线程。
  • 释放 lock 锁,并唤醒⼀个标记为 notFull(生产者)的线程。
public E take() throws InterruptedException {final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == 0)notEmpty.await();return dequeue();} finally {lock.unlock();}
}private E dequeue() {// assert lock.getHoldCount() == 1;// assert items[takeIndex] != null;final Object[] items = this.items;@SuppressWarnings("unchecked")E x = (E) items[takeIndex];items[takeIndex] = null;if (++takeIndex == items.length)takeIndex = 0;count--;if (itrs != null)itrs.elementDequeued();notFull.signal();return x;
}

相关知识点:
Java 并发编程面试题——Condition 接口

5.BlockingQueue 的使用场景有哪些?

5.1.生产者—消费者模式

public class Test {int queueSize = 10;BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(queueSize);public static void main(String[] args) {Test test = new Test();Producer producer = test.new Producer();Consumer consumer = test.new Consumer();producer.start();consumer.start();}class Consumer extends Thread {@Overridepublic void run() {consume();}private void consume() {while (true) {try {queue.take();System.out.println("从队列取走⼀个元素,队列剩余 " + queue.size() + " 个元素");} catch (InterruptedException e) {e.printStackTrace();}}}}class Producer extends Thread {@Overridepublic void run() {produce();}private void produce() {while (true) {try {queue.put(1);System.out.println("向队列取中插入⼀个元素,队列剩余空间:" + (queue.size()));} catch (InterruptedException e) {e.printStackTrace();}}}}
}

上述代码的一个结果片段如下:

从队列取⾛⼀个元素,队列剩余 0 个元素 
从队列取⾛⼀个元素,队列剩余 0 个元素 
向队列取中插⼊⼀个元素,队列剩余空间:9 
向队列取中插⼊⼀个元素,队列剩余空间:9 
向队列取中插⼊⼀个元素,队列剩余空间:9 
向队列取中插⼊⼀个元素,队列剩余空间:8 
向队列取中插⼊⼀个元素,队列剩余空间:7 
向队列取中插⼊⼀个元素,队列剩余空间:6 
向队列取中插⼊⼀个元素,队列剩余空间:5 
向队列取中插⼊⼀个元素,队列剩余空间:4 
向队列取中插⼊⼀个元素,队列剩余空间:3 
向队列取中插⼊⼀个元素,队列剩余空间:2 
向队列取中插⼊⼀个元素,队列剩余空间:1 
向队列取中插⼊⼀个元素,队列剩余空间:0 
从队列取⾛⼀个元素,队列剩余 1 个元素 
从队列取⾛⼀个元素,队列剩余 9 个元素

注意,这个例子中的输出结果看起来可能有问题,比如有几行在插入⼀个元素之后,队列的剩余空间不变。这是由于 System.out.println 语句没有锁。考虑到这样的情况:线程 1 在执行完 put/take 操作后立即失去 CPU 时间片,然后切换到线程 2 执行 put/take 操作,执行完毕后回到线程 1 的 System.out.println 语句并输出,发现这个时候阻塞队列的 size 已经被线程 2 改变了,所以这个时候输出的 size 并不是当时线程 1 执行完 put/take 操作之后阻塞队列的 size,但可以确保的是 size 不会超过 10 个。实际上使用阻塞队列是没有问题的。

5.2.线程池中使用阻塞队列

(1)ThreadPoolExecutor.java 中的一个构造函数源码如下:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}

(2)Java 中的线程池就是使用阻塞队列实现的,我们在了解阻塞队列之后,无论是使用 Exectors 类中已经提供的线程池,还是自己通过 ThreadPoolExecutor 实现线程池,都会比较方便。

6.✨手动实现一个简单的阻塞队列?

(1)以下是一个使用 Java 手写的基本阻塞队列的示例:

import java.util.LinkedList;
import java.util.Queue;public class BlockingQueue<T> {private Queue<T> queue;private int capacity;public BlockingQueue(int capacity) {this.queue = new LinkedList<>();this.capacity = capacity;}public synchronized void enqueue(T item) throws InterruptedException {while (queue.size() == capacity) {wait(); // 如果队列已满,等待直到有空间}queue.add(item);notifyAll(); // 通知其他线程队列中有新元素}public synchronized T dequeue() throws InterruptedException {while (queue.isEmpty()) {wait(); // 如果队列为空,等待直到有元素可出队}T item = queue.poll();notifyAll(); // 通知其他线程队列中有空间return item;}
}

上述代码定义了一个泛型的阻塞队列类 BlockingQueue,基于一个内部的 LinkedList 实现队列。

  • 构造函数接受一个容量参数用于限制队列的大小。
  • enqueue 方法用于将元素添加到队尾。如果队列已满,则当前线程进入等待状态,直到有空间可用。添加元素后,使用 notifyAll() 方法通知其他线程有新元素可用。
  • dequeue 方法用于从队头获取并移除一个元素。如果队列为空,则当前线程进入等待状态,直到有元素可出队。获取元素后,使用notifyAll()方法通知其他线程有空间可用。

(2)请注意,这只是一个简单的示例,可能还需要进行更多的安全性和异常处理的优化。此外,阻塞队列的实现可以有多种方式,这只是其中的一种。在实际使用时,你还需要根据具体需求进行适当的调整和扩展。

(3)InterruptedException 的说明:

  • InterruptedException 是 Java 中的一个异常类,用于表示在线程处于阻塞状态时被中断的情况。当一个线程调用了处于阻塞状态的方法(如 Thread.sleep()Object.wait()BlockingQueue.take() 等)时,如果该线程被其他线程调用了 interrupt() 方法中断,那么该阻塞方法将抛出 InterruptedException 异常。
  • InterruptedException 是一个受检查的异常,意味着在使用阻塞方法时必须显式地处理或传递这个异常。当抛出 InterruptedException 时,线程的中断状态将被清除,即 Thread.interrupted() 方法会返回 false。
  • 主要情况下,我们对 InterruptedException 的处理方式通常有两种:
    • 向上抛出异常:在方法的签名中声明 throws InterruptedException,将异常传递给调用者处理。这要求调用者必须在调用时处理或继续传递该异常。
    • 恢复中断状态:在 catch 块中捕获 InterruptedException,然后根据需要重新中断线程,通常是通过调用 Thread.currentThread().interrupt() 方法。这样做是为了保持线程的中断状态,以便其他线程能够检测到中断并做出响应。

下面是一个示例,展示了如何处理 InterruptedException:

public void myMethod() {try {// 执行可能会抛出 InterruptedException 的操作Thread.sleep(1000);} catch (InterruptedException e) {// 恢复线程的中断状态Thread.currentThread().interrupt();// 可以进行其他的处理System.out.println("Caught InterruptedException: " + e.getMessage());}
}

在上述示例中,myMethod 方法使用 Thread.sleep() 方法模拟一个可能被中断的操作。当线程在执行 sleep 方法时被中断时,将抛出 InterruptedException。在 catch 块中,我们恢复线程的中断状态,并进行其他适当的处理,如打印异常信息。通过正确处理 InterruptedException,我们可以更好地响应线程的中断请求,并采取适当的措施来维护线程的中断状态。

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

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

相关文章

java项目值水果销售管理网站(ssm源码+文档)

项目简介 水果销售管理网站实现了以下功能&#xff1a; 管理员&#xff1a;主页、个人中心、用户管理、水果信息管理、水果类别管理、销售单位管理、水果销售分析管理、会员分析管理、系统管理、订单管理。用户前台&#xff1a;首页、水果信息、新闻资讯、我的、跳转到后台、…

我用PYQT5做的第一个实用的上位机项目(二)

从这篇开始&#xff0c;复盘整个的过程&#xff0c;做一个记录。 首先&#xff0c;制作一些自定义的常用部件&#xff0c;原生的部件很粗糙。 一、按钮的图片资源&#xff1a;用绘图软件&#xff08;例如AI、coreldraw、PS等&#xff0c;看自己的熟悉程度&#xff09;制作按钮…

2009-2018年各省涉农贷款数据(wind)

2009-2018年各省涉农贷款数据&#xff08;wind&#xff09; 1、时间&#xff1a;:209-2018年 2、范围&#xff1a;31省 3、来源&#xff1a;wind 4、指标&#xff1a;涉农贷款 指标解释 &#xff1a;在涉农贷款的分类上&#xff0c;按照城乡地域将涉农贷款分为农村贷款和城…

新手程序员怎么接单?

程序员如何在自己年富力强的时候&#xff0c;最大化发挥自己的能力&#xff1f;将超能力转化为“钞能力”&#xff1f; 有人还在苦哈哈当老黄牛&#xff0c;一身使不完的牛劲&#xff0c;有人已经另辟蹊径&#xff0c;开创了自己的一片致富小天地。 接单找兼职&#xff0c;就…

springcloud之微服务简介

写在前面 本文看下微服务的基础内容&#xff0c;并对springcloud做一个简单的介绍。 1&#xff1a;为什么需要微服务 记得工作的前五六年&#xff0c;项目基本上都是一个大的单体应用&#xff0c;大家都是在同一个分支开发以及提交代码&#xff0c;如下图是之前一个单体应用…

【小余送书第一期】《数据要素安全流通》参与活动,即有机会中奖哦!!

目录 1、背景介绍 2、本书编撰背景 3、本书亮点 4、本书主要内容 5、活动须知 1、背景介绍 随着大数据、云计算、人工智能等新兴技术的迅猛发展&#xff0c;数据已经成为我国经济社会发展的五大生产要素之一&#xff0c;《网络安全法》《个人信息保护法》《数据安全法》的…

Jmeter+Ant+Git+Jenkins持续集成介绍

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 一 简介 1.什么是ant? ant是构建工具 2.什么是构建 概念到处可查到&#xff0c;形象来说&#xff…

【NLP的Python库(04/4)】:Flair

一、说明 Flair是一个现代的NLP库。从文本处理到文档语义&#xff0c;支持所有核心 NLP 任务。Flair使用现代转换器神经网络模型来完成多项任务&#xff0c;并结合了其他Python库&#xff0c;可以选择特定的模型。其清晰的API和注释文本的数据结构&#xff0c;以及多语言支持&a…

[C]精炼分析状态机FSM

FSM&#xff1a;finite state machine 【有限状态机】&#xff0c;用通俗的语言来表达就是逻辑流程图。 当前状态满足触发条件时&#xff0c;就会切换到下一个状态&#xff0c;并执行对应的任务操作。传统代码做法是用if-else 或者 switch-case来处理。若要做到可扩展性良好的…

正确设置PyTorch训练时使用的GPU资源

背景&#xff1a; 最近在使用Hugging Face的transformers api来进行预训练大模型的微调&#xff0c;机器是8卡的GPU&#xff0c;当我调用trainer.train()之后&#xff0c;发现8个GPU都被使用了&#xff0c;因为这个机器上面还有其他人跑的模型&#xff0c;当我进行训练的时候&…

Qt使用I.MX6U开发板上的按键(原理:将电脑键盘方向键↓在Qt中的枚举值与开发板中按键定义的枚举值一致,这样电脑端测试效果就与开发板的一致)

在上篇介绍了Qt点亮I.MX6U开发板的一个LED&#xff0c;对于Qt控制I.MX6U开发板的一个蜂鸣器原理也是一样的&#xff0c;就不做详细介绍&#xff0c;具体可参考Qt控制I.MX6U开发板的一个蜂鸣器&#xff0c;本篇介绍Qt使用I.MX6U开发板上的按键的相关内容。 文章目录 1. 开发板硬…

【Verilog 教程】6.5 Verilog避免Latch

关键词&#xff1a;触发器&#xff0c;锁存器 Latch 的含义 锁存器&#xff08;Latch&#xff09;&#xff0c;是电平触发的存储单元&#xff0c;数据存储的动作取决于输入时钟&#xff08;或者使能&#xff09;信号的电平值。仅当锁存器处于使能状态时&#xff0c;输出才会随着…

HC32 IIC/I2C读写

IIC状态码 IIC 初始化 void iicInit(uint32_t speed) {stc_gpio_cfg_t stcGpioCfg;DDL_ZERO_STRUCT(stcGpioCfg);Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE); //开启GPIO时钟门控stcGpioCfg.enDir GpioDirOut; ///< 端口方向配置…

【2023最新版】超详细NMAP安装保姆级教程,Nmap的介绍、功能并进行网络扫描,收藏这一篇就够了

简介 从诞生之初&#xff0c;Nmap就一直是网络发现和攻击界面测绘的首选工具。从主机发现和端口扫描&#xff0c;到操作系统检测和IDS规避/欺骗&#xff0c;Nmap是大大小小黑客行动的基本工具。 为了绘制网络拓扑图&#xff0c;Nmap的发送特制的数据包到目标主机&#xff0c;…

【Amazon】AI 代码生成器—Amazon CodeWhisperer初体验 | 开启开挂编程之旅

使用 AI 编码配套应用程序更快、更安全地构建应用程序 文章目录 1.1 Amazon CodeWhisperper简介1.2 Amazon CodeWhisperer 定价2.1 打开VS Code2.2 安装AWS ToolKit插件 一、前言 1.1 Amazon CodeWhisperper简介 1️⃣更快地完成更多工作 CodeWhisperer 经过数十亿行代码的训…

目标检测如何演变:从区域提议和 Haar 级联到零样本技术

目录 一、说明 二、目标检测路线图 2.1 路线图&#xff08;一般&#xff09; 2.2 路线图&#xff08;更传统的方法&#xff09; 2.3 路线图&#xff08;深度学习方法&#xff09; 2.4 对象检测指标的改进 三、传统检测方法 3.1 维奥拉-琼斯探测器 (2001) 3.2 HOG探测器…

为什么u盘在mac上显示不出来

插入U盘是个看似简单的操作&#xff0c;但有时候在Mac电脑上却出现了无法显示U盘的情况。这样的问题是非常让人头疼的&#xff0c;特别是当你急需使用U盘中的文件时。那么&#xff0c;究竟为什么U盘在Mac上会显示不出来呢&#xff1f;今天就让我们一起来深入了解一下这个问题&a…

编程新时代:Amazon CodeWhisperer 助您轻松驾驭代码世界

文章目录 一、什么是 Amazon CodeWhisperer&#xff1f;二、个人无限免费使用三、安装配置3.1 手把手教你在pycharm配置3.2 同理在VSCODE安装 三、Pycharm上测试3.1 根据注释写代码3.2 检查修复代码错误3.3 构建一个简单爬虫 四、 VSCODE上测试4.1 个性化体验4.2 系统兼容性4.3…

【再识C进阶3(下)】详细地认识字符分类函数,字符转换函数和内存函数

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

vuereact质检工具(eslint)安装使用总结

1、ESLint ESLint工具主要类似java中的checkStyle和findbugs&#xff0c;是检查代码样式和逻辑规范的工具。 1.1、ESLint安装流程 打开VSCode软件&#xff0c;打开扩展中心&#xff0c;下载ESLint插件 图1.1 点击后面的install按进行安装&#xff0c;如图1.2所示&#xff1…