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

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 的示例如下:

//元素实现 Delayed 接口
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());}}
}

输出结果如下:

取出元素:Element 2
取出元素:Element 1
取出元素:Element 3

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 的实现原理是什么?

阻塞队列的原理由通知模式实现。所谓通知模式,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。在源代码中,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.signal() 方法唤醒⼀个标记为 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.signal() 方法唤醒⼀个标记为 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,我们可以更好地响应线程的中断请求,并采取适当的措施来维护线程的中断状态。

相关文章:

Java 并发编程面试题——BlockingQueue

目录 1.什么是阻塞队列 (BlockingQueue)&#xff1f;2.BlockingQueue 有哪些核心方法&#xff1f;3.BlockingQueue 有哪些常用的实现类&#xff1f;3.1.ArrayBlockingQueue3.2.DelayQueue3.3.LinkedBlockingQueue3.4.PriorityBlockingQueue3.5.SynchronousQueue 4.✨BlockingQu…...

Ubuntu Nacos开机自启动服务

1、创建service文件 在/lib/systemd/system目录下创建nacos.service文件 [Unit] Descriptionalibaba nacos Afternetwork.target Documentationhttps://nacos.io/zh-cn/[Service] Userroot Grouproot Typeforking Environment"JAVA_HOME/usr/local/programs/jdk-8u333-li…...

C++核心编程--继承篇

4.6、继承 继承是面向对象三大特征之一 有些类与类之间存在特殊的关系&#xff0c;例如下图中&#xff1a; ​ 我们发现&#xff0c;定义这些类的定义时&#xff0c;都拥有上一级的一些共性&#xff0c;还有一些自己的特性。那么我们遇到重复的东西时&#xff0c;就可以考虑使…...

小程序 解决自定义弹窗滚动穿透问题,解决弹窗背景内容滚动问题

方法一、catchtouchmove"true"&#xff0c; 可以实现弹框背景不滚动&#xff0c;但是也会导致弹框自身无法滚动&#xff0c;如果你的弹窗本身是不需要滚动的&#xff0c;用这个方法是极佳的。 <view class"pop" catchtouchmove"true"> …...

win10搭建Selenium环境+java+IDEA(2)

接着上一个搭建环境开始叙述&#xff1a;win10系统x64安装java环境以及搭建自动化测试环境_荟K的博客-CSDN博客 上一步结尾的浏览器驱动&#xff0c;本人后面改到了谷歌浏览器.exe文件夹下&#xff1a; 这里需要注意&#xff0c;这个新路径要加载到系统环境变量中。 上一步下…...

抢先一步感受未来:Raspberry Pi 5正式发布!

在经历了几年全球供应链困境导致 Raspberry Pi 单板计算机的产能降低和零售价格上涨之后&#xff0c;今天终于迎来了更新。Raspberry Pi 4 上市四年后&#xff0c;今天Raspberry Pi 5正式发布&#xff01;新推出的 Raspberry Pi 5 配备了经过大幅改进升级的SoC&#xff0c;带来…...

【教程】Ubuntu自动查看有哪些用户名与密码相同的账户,并统一修改密码

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 目录 背景说明 开始操作 修改密码 背景说明 有些用户为了图方便或者初始创建用户默认设置等原因&#xff0c;会将密码设置为与用户名相同&#xff0c;但这就使得非常不安全。甚至如果该用户具有sudo权限&#…...

基于 Python+DenseNet121 算法模型实现一个图像分类识别系统

项目展示 一、介绍 DenseNet&#xff08;Densely Connected Convolutional Networks&#xff09;是一种卷积神经网络&#xff08;CNN&#xff09;架构&#xff0c;2017年由Gao Huang等人提出。该网络的核心思想是密集连接&#xff0c;即每一层都接收其前面所有层的输出作为输…...

贪心算法-点灯问题

1、题目描述 给定一个字符串str&#xff0c;只由 ‘X’ 和 ‘.’ 两种字符构成。‘X’ 表示墙&#xff0c;不能放灯&#xff0c;点亮不点亮都可&#xff1b;’.’ 表示居民点&#xff0c;可以放灯&#xff0c;需要点亮。如果灯放在i位置&#xff0c;可以让 i-1&#xff0c;i 和…...

软件测试之单元测试自动化入门基础

单元测试自动化 所谓的单元测试(Unit Test)是根据特定的输入数据&#xff0c;针对程序代码中的最小实体单元的输入输出的正确性进行验证测试的过程。所谓的最小实体单元就是组织项目代码的最基本代码结构&#xff1a;函数&#xff0c;类&#xff0c;模块等。在Python中比较知名…...

93 # 实现 express 错误处理中间件

上一节实现了 express 的中间件&#xff0c;这一节来实现错误处理中间件 执行某一步出错了&#xff0c;统一规定调用 next 传递的参数就是错误信息 先看 express 实现的demo const express require("express"); const app express();app.use("/", (re…...

PHP 创建 MySQL 表

目录 PHP 创建 MySQL 表 使用 MySQLi 和 PDO 创建 MySQL 表 实例 (MySQLi - 面向对象) 实例 (MySQLi - 面向过程) 实例 (PDO) PHP 创建 MySQL 表 一个数据表有一个唯一名称&#xff0c;并有行和列组成。 使用 MySQLi 和 PDO 创建 MySQL 表 CREATE TABLE 语句用于创建 MySQ…...

中兴R5300 G4服务器iSAC管理员zteroot密码遗失的重置方法及IPV6地址启用设置

本文讲解中兴R5300 G4服务器BMC带外iSAC管理员zteroot密码遗失&#xff0c;无法登录时如何对其进行密码重置&#xff0c;以及iSAC启用IPV6地址的方法。 一、重置中兴R5300 G4服务器iSAC管理员zteroot密码 1、通过SSH登录到iSAC&#xff0c;默认用户名&#xff1a;sysadmin&am…...

大数据分布式处理框架Hadoop

大数据是什么 大数据容量常以TB、PB、甚至EB为单位&#xff0c;远超传统数据库的承载能力&#xff0c;无论入库还是查询都出现性能瓶颈。 Hadoop是什么 Hadoop是开源的分布式计算技术框架&#xff0c;用于处理大规模数据和实现分布式存储。 Hadoop核心组件 HDFS&#xff08;…...

echarts学习总结

一、新建一个简单的Echarts 1、首先新建一个vue2的项目&#xff0c;项目中安装Echarts cnpm install echarts --save2、新建一个ref <template><div ref"myecharts" id"myecharts"></div> </template> 3、引入echarts <scri…...

与初至波相关的常见误解

摘要: 初至波是指检波器首次接收到的波. 对它的误解会使我们失去重要的信息. 1. 波从震源到检波器的传导过程 从震源产生波以后, 有些波通过地面直接传导到检波器, 这些称为直达波 (面波);有些在地层中传播,遇到两种地层的分界面时 产生波的反射,在原来地层中形成一种新波, …...

screenfull全屏、退出全屏、指定元素全屏的使用步骤

文章目录 页面全屏页面全屏完整代码 1.下载插件 建议下载指定版本5.1.0&#xff0c;不然可能有一个报错 npm install --save screenfull5.1.02.页面引入 import screenfull from "screenfull"页面全屏 3.在标签上绑定点击事件 <div click"handleFull"…...

问题 - 谷歌浏览器 network 看不到接口请求解决方案

谷歌浏览器 -> 设置 -> 重置设置 -> 将设置还原为其默认值 查看接口情况&#xff0c;选择 All 或 Fetch/XHR&#xff0c;勾选 Has blocked cookies 即可 如果万一还不行&#xff0c;卸载浏览器重装。 参考&#xff1a;https://www.cnblogs.com/tully/p/16479528.html...

Java:正则表达式的命名捕获组

命名捕获组格式 (?<year>.*)-(?<month>.*)-(?<date>.*)完整示例 package com.example.demo;import java.util.regex.Matcher; import java.util.regex.Pattern;public class RegexTests {public static void main(String[] args) {String text "2…...

ELK 处理 Spring Boot 日志

ELK 处理 Spring Boot 日志&#xff0c;妙啊&#xff01; 来源&#xff1a;ibm.com/developerworks/cn/java /build-elk-and-use-it-for-springboot -and-nginx/index.html ELK 简介 Logstash Elasticsearch Kibana ELK 实现方案 ELK 平台搭建 安装 Logstash 安装 Elas…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存

文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

CSS | transition 和 transform的用处和区别

省流总结&#xff1a; transform用于变换/变形&#xff0c;transition是动画控制器 transform 用来对元素进行变形&#xff0c;常见的操作如下&#xff0c;它是立即生效的样式变形属性。 旋转 rotate(角度deg)、平移 translateX(像素px)、缩放 scale(倍数)、倾斜 skewX(角度…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

【笔记】结合 Conda任意创建和配置不同 Python 版本的双轨隔离的 Poetry 虚拟环境

如何结合 Conda 任意创建和配置不同 Python 版本的双轨隔离的Poetry 虚拟环境&#xff1f; 在 Python 开发中&#xff0c;为不同项目配置独立且适配的虚拟环境至关重要。结合 Conda 和 Poetry 工具&#xff0c;能高效创建不同 Python 版本的 Poetry 虚拟环境&#xff0c;接下来…...