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

java数据结构--阻塞队列

目录

一.概念

二.生产者消费者问题

三.阻塞队列接口BlockingQueue

四.基于数组实现单锁的阻塞队列

1.加锁方式

2.代码实现

3.解释说明

(1).offer添加元素

(2)poll取出元素

4.timeout超时时间

5.测试

五.基于数组实现双锁的阻塞队列

1.问题

2.关于size共享变量

3.死锁问题

 4.级联唤醒

(1)offer中只唤醒一次,其他交给poll线程唤醒

(2)poll中只唤醒一次,其他交给offer线程唤醒

5.完整代码


一.概念

阻塞队列是一种特殊类型的队列,具有额外的阻塞操作。在阻塞队列中,当队列为空时,从队列中获取元素的操作会被阻塞,直到有元素被添加到队列中为止;当队列满时,向队列中添加元素的操作会被阻塞,直到队列有空闲位置为止。

阻塞队列在多线程编程中非常有用,可以有效地进行线程间的协调和通信。它提供了一种线程安全的方式来共享数据,避免了常见的并发问题,如资源争用和死锁。

常见的阻塞队列有以下几种实现方式:

  1. ArrayBlockingQueue:基于数组实现的有界阻塞队列,需要指定队列的容量。
  2. LinkedBlockingQueue:基于链表实现的可选有界或无界阻塞队列。
  3. PriorityBlockingQueue:基于堆实现的无界优先级阻塞队列,元素按照优先级进行排序。
  4. SynchronousQueue:特殊的阻塞队列,每个插入操作必须等待一个对应的删除操作,反之亦然。

阻塞队列提供了一些常用的操作方法,如put()和take()等。put()方法用于向队列中添加元素,并在队列满时阻塞调用线程;take()方法用于获取队列中的元素,并在队列为空时阻塞调用线程。

使用阻塞队列可以简化并发编程的实现,提高代码的可读性和维护性。它能够有效地控制线程的访问顺序,并提供了一种直观的方式来处理线程间的同步问题。

二.生产者消费者问题

 我们先来分析一下,在我们之前学过的操作系统中,有一个生产者和消费者问题,当生产者每次生产完一个产品后,要往缓冲区中放入,但是如果此时缓冲区是满的,那么就要让生产者进入阻塞状态,进行等待,当消费者从缓冲区中取出一个产品消费后,缓冲区有了空位,此时生产者就被唤醒,往缓冲区中放入生产的产品,如果有多个生产者和消费者,此时为了防止出现混乱,就要在放入缓冲区之前加锁,放入后解锁,消费者在从缓冲区取之前也要加锁,取出后再解锁

三.阻塞队列接口BlockingQueue

/*** 阻塞队列接口定义* @param <E>*/
public interface BlockingQueue<E> {void offer(E e) throws InterruptedException;boolean offer(E e,long timeout) throws InterruptedException;E poll() throws InterruptedException;E poll(long timeout) throws InterruptedException;boolean isFull();boolean isEmpty();}

  我们定义了接口,其中,主要就是成对的两个方法,offer和poll,还有在此基础上加入超时时间的,加入超时时间是,当队列满时,超过了等待时间,就不去添加了,直接返回失败false

四.基于数组实现单锁的阻塞队列

1.加锁方式

  这里我们先说明一下,在java中,可以有两种方式加锁

  1.synocized关键字加锁

 2.ReentrantLock类创建可重入锁对象,该对象还可以创建出条件对象,来执行阻塞和唤醒操作

2.代码实现

  我们先来用单锁实现一下阻塞队列

/*** 基于数组实现阻塞队列* @param <E>*/
public class ArrayBlockQueue<E> implements BlockingQueue<E>{private final E[] array;private int head;private int tail;private int size;@SuppressWarnings("all")public ArrayBlockQueue(int capacity){array = (E[]) new Object[capacity];}//锁对象private final ReentrantLock reentrantLock = new ReentrantLock();//条件对象private final Condition headWaits = reentrantLock.newCondition();private final Condition tailWaits = reentrantLock.newCondition();/*** 向队尾添加元素,如果队列为满,则阻塞当前线程* @param e 要添加元素* @throws InterruptedException*/@Overridepublic void offer(E e) throws InterruptedException {//先加锁reentrantLock.lock();try{//判断队列是否为满,这里要用while循环来判断,防止虚假唤醒while (isFull()){//如果是满的,就让当前线程阻塞tailWaits.await();  //因为是向队尾添加元素,所以用tailWaits}//当队列不满时,可以进行添加array[tail] = e;//先判断tail是否越界if(++tail == array.length){tail = 0;}//让size+1size++;//这时要通知poll,因为放入了一个元素,所以队列肯定不为空,通知poll线程可以取元素了headWaits.signal(); //唤醒poll线程}finally {//释放锁reentrantLock.unlock();}}/*** 向队尾添加元素,加入超时时间,如果队满,并且过了超时时间,返回false* @param e 要添加的元素* @param timeout 超时时间* @return 是否添加成功* @throws InterruptedException*/@Overridepublic boolean offer(E e, long timeout) throws InterruptedException {//先加锁reentrantLock.lock();try{//将传入的时间转换为纳秒long t = TimeUnit.MILLISECONDS.toNanos(timeout);//判断队列是否为满,这里要用while循环来判断,防止虚假唤醒while (isFull()){/***  如果是满的,就让当前线程阻塞*  我们用加入时间的方法来阻塞*  注意这里我们每次阻塞完唤醒后,就更新等待时间*  如果要等待的时间是5s,那么如果等待了1s发现队列有空,那么唤醒之后,*  如果是虚假唤醒,就要再次等待,那么下次等待时间就是4s*  如果等待时间 t<=0了,说明等待超时,直接返回false,不要在等了**/if(t <= 0){return false;}t = tailWaits.awaitNanos(t);}//当队列不满时,可以进行添加array[tail] = e;//先判断tail是否越界if(++tail == array.length){tail = 0;}//让size+1size++;//这时要通知poll,因为放入了一个元素,所以队列肯定不为空,通知poll线程可以取元素了headWaits.signal(); //唤醒poll线程/*** 到这里就说明添加成功,返回true*/return true;}finally {//释放锁reentrantLock.unlock();}}/*** 移除队头元素,如果队列为空,则阻塞当前线程* @return 队头元素* @throws InterruptedException*/@Overridepublic E poll() throws InterruptedException {//先加锁reentrantLock.lock();try {//先循环判断队列是否为空while (isEmpty()){//如果队列为空,让当前线程阻塞headWaits.await();}//如果队列不为空,可以取了E e = array[head];array[head] = null; //help GC//判断head是否越界if(++head == array.length){head = 0;}//让size-1size--;//这时队列因为取出了一个元素,所以肯定不为满,通知offer线程可以添加元素了tailWaits.signal(); //唤醒offer线程return e;}finally {//释放锁reentrantLock.unlock();}}/*** 移除队头元素,加入超时时间,如果队空,并且超过等待时间,返回null* @param timeout 超时时间* @return 队头元素* @throws InterruptedException*/@Overridepublic E poll(long timeout) throws InterruptedException {//先加锁reentrantLock.lock();try {long t = TimeUnit.MILLISECONDS.toNanos(timeout);//先循环判断队列是否为空while (isEmpty()){//如果队列为空,让当前线程阻塞if(t <= 0){return null;}t = headWaits.awaitNanos(t);}//如果队列不为空,可以取了E e = array[head];array[head] = null; //help GC//判断head是否越界if(++head == array.length){head = 0;}//让size-1size--;//这时队列因为取出了一个元素,所以肯定不为满,通知offer线程可以添加元素了tailWaits.signal(); //唤醒offer线程return e;}finally {//释放锁reentrantLock.unlock();}}@Overridepublic boolean isFull() {return size == array.length;}@Overridepublic boolean isEmpty() {return size == 0;}@Overridepublic String toString() {return Arrays.toString(array);}
}

3.解释说明

注释中也都写了,我在这里再次解释一下:

(1).offer添加元素

  当我们执行offer方法向队列中添加元素时:

 1. 我们需要先加锁,

 2. 我们判断队列是否为满,

 3.  如果满了,我们需要让当前线程阻塞 ,

  注意:这里使用的是while循环,为什么不用if呢?我们想,如果用if,那么它只会判断一次,如果当某个时刻,队列从满变为不满,这时我们阻塞的offer线程被唤醒,将要去添加元素,但就在此时,另一个offer1线程可能在offer线程添加之前抢先往队列中添加了元素,那么offer线程再去添加就会报错,也就是虚假唤醒(spurious wakeups),使用while循环判断队列是否为满,可以在阻塞线程被唤醒后重新判断队列是否满足条件。如果队列仍然满,线程会继续被阻塞,直到队列有空闲位置。这样可以预防虚假唤醒的问题,确保线程只有在满足条件的情况下才会执行添加元素的操作。

 4.当while条件不成立时,也就是队列有空为,并且此时没有其他线程来争抢,那么就可以往队列中添加元素了 , queue[tail] = e;

5.判断++tail是否达到了数组末尾位置,如果到了,那么重新调整为0,相当于一个圆圈

6.让size++

7.当offer线程向队列中添加元素后,此时队列肯定不为空,我们应该向poll线程发出信号,可以唤醒,相当于操作系统中的信号量机制

(2)poll取出元素

    1.先加锁

    2.判断队列是否为空,同理使用while循环判断

    3.当队列不为空时,取出队头元素

    4.判断++head是否到数组末尾位置,如果到了,重新置为0

    5.让size--;

    6.当poll线程从队列中取出元素后,队列肯定不为满,我们应该向offer线程发出信号,唤醒offer线程

4.timeout超时时间

 我们可以为offer和poll设置超时时间,当超过了等待时间,将直接返回,不在执行

 看一下offer方法设置timeout

    /**
                 *  如果是满的,就让当前线程阻塞
                 *  我们用加入时间的方法来阻塞
                 *  注意这里我们每次阻塞完唤醒后,就更新等待时间
                 *  如果要等待的时间是5s,那么如果等待了1s发现队列有空,那么唤醒之后,
                 *  如果是虚假唤醒,就要再次等待,那么下次等待时间就是4s
                 *  如果等待时间 t<=0了,说明等待超时,直接返回false,不要在等了
                 **/

poll方法同理... 

5.测试

 下面让我们来测试一下代码

  

public class TestBlockQueue {public static void main(String[] args) throws InterruptedException {ArrayBlockQueue<String> queue = new ArrayBlockQueue<>(4);queue.offer("task1");queue.offer("task2");queue.offer("task3");queue.offer("task4");System.out.println(queue);new Thread(() -> {System.out.println(Thread.currentThread().getName() + "开始添加元素...");try {boolean flag = queue.offer("task5", 4000);if(flag){System.out.println(Thread.currentThread().getName() + "添加元素成功....");}else {System.out.println(Thread.currentThread().getName()+"添加元素超超时失败....");}System.out.println(queue);} catch (InterruptedException e) {throw new RuntimeException(e);}}, "t1").start();}
}

 我们给队列初始大小设为4,然后向队列中添加4个任务,此时队列为满,然后我们开启一个t1线程向队列中添加元素,设置超时时间为4s,判断是否能添加成功

运行:

[task1, task2, task3, task4]
t1开始添加元素...
t1添加元素超超时失败....
[task1, task2, task3, task4]进程已结束,退出代码0

可以看到,过了4s,添加失败,因为我们并没有取出任何元素,所以offer线程一直阻塞直到超时失败!

下面,我们让主线程先休眠2s,然后取出一个元素,再次观察t1是否能添加成功:

        //让主线程休眠2s,然后pollThread.sleep(2000);queue.poll();

在上面的代码中添加以上代码,然后运行:

[task1, task2, task3, task4]
t1开始添加元素...
t1添加元素成功....
[task5, task2, task3, task4]进程已结束,退出代码0

这次可以看到,task5被成功的添加到了队列中,因为我们在超时时间之前取出了队列的一个元素,队列有了空位,task5就可以添加到队列中了。

五.基于数组实现双锁的阻塞队列

1.问题

 上面我们是用一把锁来给offer线程和poll线程加锁,他们两个操作用的同一把锁,这样其实并不好,效率比较低,而且添加和取出操作应该是两个互不影响的操作,是互相解耦的,所以我们应该使用双锁来给他们分别加锁和释放锁

2.关于size共享变量

  当我们换成双锁后,需要思考一个问题,这个头指针head和尾指针tail,head是poll线程用来取出队列头元素的,tail是offer线程用来向队尾添加元素使用的,所以说head和tail这两个变量是互不影响的,它们分别在各自的线程里使用,但是对于size,在offer线程中,最后要让size++;在poll线程中,最后要让size--,这就是共享的变量了,可能会出现线程安全问题,如果两个线程不是顺序执行的,而是交错执行,就会是size的值发生混乱,所以我们要对size作约束

 在java中,我们可以实现原子类来对变量进行线程安全保护,对于int类型的我们使用AtomicInteger

加一可以使用getAndIncreament()方法,减一可以使用getAndDecreament()方法

    //offer锁private final ReentrantLock headLock = new ReentrantLock();//poll锁private final ReentrantLock tailLock = new ReentrantLock();//条件对象private final Condition headWaits = headLock.newCondition();private final Condition tailWaits = tailLock.newCondition();

3.死锁问题

  如果我们添加了双锁,那么我们需要设置各自的条件去阻塞和唤醒线程,先看一下offer线程,

    @Overridepublic void offer(E e) throws InterruptedException {//先加锁tailLock.lock();try{//判断队列是否为满,这里要用while循环来判断,防止虚假唤醒while (isFull()){//如果是满的,就让当前线程阻塞tailWaits.await();  //因为是向队尾添加元素,所以用tailWaits}//当队列不满时,可以进行添加array[tail] = e;//先判断tail是否越界if(++tail == array.length){tail = 0;}//让size+1size.getAndIncrement();headLock.lock();try {headWaits.signal(); //poll1}finally {headLock.unlock();}}finally {//释放锁tailLock.unlock();}}}

注意看,这里我们用headWaits唤醒线程时,它是在tailLock释放锁之前,也就是一个嵌套结构,这样就会导致死锁的发生,

 

如果tailLock先加锁了,然后headLock也去加锁,之后在offer线程中的headLock想去加锁就加不上了,同理poll线程中的tailLock想加锁也加不上去,他们两个线程互相僵持,陷入了死锁状态,为了防止死锁发生,我们只要把嵌套结构改为平级结构就可以了,这样就能保证一定是释放完锁之后再去加锁,一定可以加锁成功!

 

   @Overridepublic void offer(E e) throws InterruptedException {//先加锁tailLock.lock();try{//判断队列是否为满,这里要用while循环来判断,防止虚假唤醒while (isFull()){//如果是满的,就让当前线程阻塞tailWaits.await();  //因为是向队尾添加元素,所以用tailWaits}//当队列不满时,可以进行添加array[tail] = e;//先判断tail是否越界if(++tail == array.length){tail = 0;}//让size+1size.getAndIncrement();}finally {//释放锁tailLock.unlock();}/*** 平级可以防止死锁* 唤醒poll线程,但是因为是各自不同的锁,所以需要在他们各自的锁内唤醒*/  headLock.lock();try {headWaits.signal(); //poll1}finally {headLock.unlock();}}

这样的平级结构就可以防止死锁发生了... 

 4.级联唤醒

  以上的唤醒逻辑,是每次都要进行一次唤醒,这样其实效率还不是最好的,那么我们可不可以减少唤醒的次数来提高效率呢?我们可以通过级联唤醒来实现

(1)offer中只唤醒一次,其他交给poll线程唤醒

  先看一下offer线程,在最后要随机唤醒一个poll线程,我们让它只唤醒一个线程,剩下的线程交给poll线程自己去唤醒,比如有poll1,poll2,poll3,poll4四个线程,那么我们的offer线程只唤醒poll1线程,然后让poll1去唤醒poll2,poll2去唤醒poll3,以此类推...,这样就可以让offer只执行一次唤醒,提高了效率

 

  @Overridepublic void offer(E e) throws InterruptedException {//记录一下每次size加一之前的值int c;//先加锁tailLock.lock();try{//判断队列是否为满,这里要用while循环来判断,防止虚假唤醒while (isFull()){//如果是满的,就让当前线程阻塞tailWaits.await();  //因为是向队尾添加元素,所以用tailWaits}//当队列不满时,可以进行添加array[tail] = e;//先判断tail是否越界if(++tail == array.length){tail = 0;}//让size+1c = size.getAndIncrement();//如果c+1>array.length,说明队列还是有空位置,就自己唤醒后面的线程if( c+1 > array.length){tailWaits.signal();}}finally {//释放锁tailLock.unlock();}/*** 平级可以防止死锁* 唤醒poll线程,但是因为是各自不同的锁,所以需要在他们各自的锁内唤醒*/if( c == 0){   //offer_0,offer_1,offer_2, c=0说明是第一个offer_0,队列时空的,准备添加第一个headLock.lock();try {/*** 这里我们只唤醒第一个poll1线程,其他的* 交给poll线程自己唤醒* 如果c为0,说明队列为空,准备要添加第一个元素,就只让offer_0来唤醒*/headWaits.signal(); //poll1}finally {headLock.unlock();}}}

   我们来看改进后的代码,先设置一个c变量来记录每次size改变前的值,在唤醒时,先判断c是否等于0,这里等于0说明队列从空的状态开始,去添加第一个元素,那么也就是第一个offer1线程,让它去唤醒poll线程,其他的poll线程交给poll线程自己去唤醒,再看一下poll的代码:

 @Overridepublic E poll() throws InterruptedException {E e;int c;//先加锁headLock.lock();try {//先循环判断队列是否为空while (isEmpty()){//如果队列为空,让当前线程阻塞headWaits.await();}//如果队列不为空,可以取了e = array[head];array[head] = null; //help GC//判断head是否越界if(++head == array.length){head = 0;}//让size-1c = size.getAndDecrement();//在这里让poll1唤醒poll2,poll2接着唤醒poll3...if( c > 1 ){//c>1说明队列中还有不止一个元素,可以继续唤醒其他poll线程来去元素headWaits.signal();}}finally {//释放锁headLock.unlock();}/*** 平级,防止死锁* 唤醒offer线程,需要加锁*  当 c == array.length时,说明队列从满变为不满,这时才去*  给tailLock加锁*/if( c == array.length ){tailLock.lock();try {tailWaits.signal();}finally {tailLock.unlock();}}return e;}

在poll代码中,只需要判断c是否大于1,如果c>1,说明队列中还有元素,可以继续唤醒,那么就让poll1去唤醒poll2,poll2去唤醒poll3.....

(2)poll中只唤醒一次,其他交给offer线程唤醒

再来看poll中,当c==array.length时,说明这时是队列从满变为不满,只有这时才去唤醒,其他情况,比如队列时不满的,也不去唤醒,

然后在offer中,当c+1<array.length时,说明即使c+1也还有空位,可以继续添加,所以由offer自己去唤醒其他offer线程,offer1唤醒offer2,offer2唤醒offer3....

5.完整代码

/*** 基于数组的双锁实现阻塞队列*/
public class ArrayDLBlockQueue<E> implements BlockingQueue<E> {private final E[] array;private int head;private int tail;//在双锁条件下,size是共享的变量,需要保证原子性private AtomicInteger size = new AtomicInteger();@SuppressWarnings("all")public ArrayDLBlockQueue(int capacity){array = (E[]) new Object[capacity];}//offer锁private final ReentrantLock headLock = new ReentrantLock();//poll锁private final ReentrantLock tailLock = new ReentrantLock();//条件对象private final Condition headWaits = headLock.newCondition();private final Condition tailWaits = tailLock.newCondition();/*** 向队尾添加元素,如果队列为满,则阻塞当前线程* @param e 要添加元素* @throws InterruptedException*/@Overridepublic void offer(E e) throws InterruptedException {//记录一下每次size加一之前的值int c;//先加锁tailLock.lock();try{//判断队列是否为满,这里要用while循环来判断,防止虚假唤醒while (isFull()){//如果是满的,就让当前线程阻塞tailWaits.await();  //因为是向队尾添加元素,所以用tailWaits}//当队列不满时,可以进行添加array[tail] = e;//先判断tail是否越界if(++tail == array.length){tail = 0;}//让size+1c = size.getAndIncrement();//如果c+1>array.length,说明队列还是有空位置,就自己唤醒后面的线程if( c+1 > array.length){tailWaits.signal();}}finally {//释放锁tailLock.unlock();}/*** 平级可以防止死锁* 唤醒poll线程,但是因为是各自不同的锁,所以需要在他们各自的锁内唤醒*/if( c == 0){   //offer_0,offer_1,offer_2, c=0说明是第一个offer_0,队列时空的,准备添加第一个headLock.lock();try {/*** 这里我们只唤醒第一个poll1线程,其他的* 交给poll线程自己唤醒* 如果c为0,说明队列为空,准备要添加第一个元素,就只让offer_0来唤醒*/headWaits.signal(); //poll1}finally {headLock.unlock();}}}/*** 向队尾添加元素,加入超时时间,如果队满,并且过了超时时间,返回false* @param e 要添加的元素* @param timeout 超时时间* @return 是否添加成功* @throws InterruptedException*/@Overridepublic boolean offer(E e, long timeout) throws InterruptedException {int c;//先加锁tailLock.lock();try{//将传入的时间转换为纳秒long t = TimeUnit.MILLISECONDS.toNanos(timeout);//判断队列是否为满,这里要用while循环来判断,防止虚假唤醒while (isFull()){/***  如果是满的,就让当前线程阻塞*  我们用加入时间的方法来阻塞*  注意这里我们每次阻塞完唤醒后,就更新等待时间*  如果要等待的时间是5s,那么如果等待了1s发现队列有空,那么唤醒之后,*  如果是虚假唤醒,就要再次等待,那么下次等待时间就是4s*  如果等待时间 t<=0了,说明等待超时,直接返回false,不要在等了**/if(t <= 0){return false;}t = tailWaits.awaitNanos(t);}//当队列不满时,可以进行添加array[tail] = e;//先判断tail是否越界if(++tail == array.length){tail = 0;}//让size+1c = size.getAndIncrement();if(c+1 < array.length){tailWaits.signal();}}finally {//释放锁tailLock.unlock();}/*** 平级防止死锁*/if( c==0){headLock.lock();try {headWaits.signal();}finally {headLock.unlock();}}return true;}/*** 移除队头元素,如果队列为空,则阻塞当前线程* @return 队头元素* @throws InterruptedException*/@Overridepublic E poll() throws InterruptedException {E e;int c;//先加锁headLock.lock();try {//先循环判断队列是否为空while (isEmpty()){//如果队列为空,让当前线程阻塞headWaits.await();}//如果队列不为空,可以取了e = array[head];array[head] = null; //help GC//判断head是否越界if(++head == array.length){head = 0;}//让size-1c = size.getAndDecrement();//在这里让poll1唤醒poll2,poll2接着唤醒poll3...if( c > 1 ){//c>1说明队列中还有不止一个元素,可以继续唤醒其他poll线程来去元素headWaits.signal();}}finally {//释放锁headLock.unlock();}/*** 平级,防止死锁* 唤醒offer线程,需要加锁*  当 c == array.length时,说明队列从满变为不满,这时才去*  给tailLock加锁*/if( c == array.length ){tailLock.lock();try {tailWaits.signal();}finally {tailLock.unlock();}}return e;}/*** 移除队头元素,加入超时时间,如果队空,并且超过等待时间,返回null* @param timeout 超时时间* @return 队头元素* @throws InterruptedException*/@Overridepublic E poll(long timeout) throws InterruptedException {E e;int c;//先加锁headLock.lock();try {long t = TimeUnit.MILLISECONDS.toNanos(timeout);//先循环判断队列是否为空while (isEmpty()){//如果队列为空,让当前线程阻塞if(t <= 0){return null;}t = headWaits.awaitNanos(t);}//如果队列不为空,可以取了e = array[head];array[head] = null; //help GC//判断head是否越界if(++head == array.length){head = 0;}//让size-1c = size.getAndDecrement();if( c > 1){headWaits.signal();}}finally {//释放锁headLock.unlock();}/*** 平级,防止死锁* 唤醒offer线程,需要加锁*/if( c == array.length ){tailLock.lock();try {tailWaits.signal();}finally {tailLock.unlock();}}return e;}@Overridepublic boolean isFull() {return size.get() == array.length;}@Overridepublic boolean isEmpty() {return size.get() == 0;}@Overridepublic String toString() {return Arrays.toString(array);}

以上就是对阻塞队列的分析了,读者可以在此基础上实现链表的实现等,我们下期再见!

相关文章:

java数据结构--阻塞队列

目录 一.概念 二.生产者消费者问题 三.阻塞队列接口BlockingQueue 四.基于数组实现单锁的阻塞队列 1.加锁方式 2.代码实现 3.解释说明 (1).offer添加元素 &#xff08;2&#xff09;poll取出元素 4.timeout超时时间 5.测试 五.基于数组实现双锁的阻塞队列 1.问题 …...

使用p2p实现Linux内网快速分发文件

安装opentracker 方法一&#xff1a;编译安装 参考如下官方文档进行操作即可&#xff0c;国内下载源码会比较慢 https://erdgeist.org/arts/software/opentracker/ 编译完成后会生成可执行文件opentracker和opentracker.debug 可以直接./opentracker.debug进行验证 方法二&a…...

Android Studio报错:connect refused

参考链接&#xff1a; https://blog.csdn.net/qq_43213783/article/details/113936012 参考文章中说报错主要是由于代理导致的&#xff0c;在文件->设置->外观与行为->系统设置->HTTP代理。 方法一&#xff1a; 查看打开代理&#xff08;前提是代理可以通网&#x…...

ubuntu 源码编译安装make过程很慢问题解决

一般下载的时候有两种方式 使用wget 下载后使用tar命令解压直接git clone 源代码 下载完成之后&#xff0c;进入目录&#xff0c;新建一个文件夹build&#xff0c;防止影响码源结构第三步&#xff0c;进入到build目录&#xff0c;使用cmake编译 cmake ..第四步直接make编译 …...

深度学习 opencv python 实现中国交通标志识别 计算机竞赛

文章目录 0 前言1 yolov5实现中国交通标志检测2.算法原理2.1 算法简介2.2网络架构2.3 关键代码 3 数据集处理3.1 VOC格式介绍3.2 将中国交通标志检测数据集CCTSDB数据转换成VOC数据格式3.3 手动标注数据集 4 模型训练5 实现效果5.1 视频效果 6 最后 0 前言 &#x1f525; 优质…...

希尔排序原理

目录&#xff1a; 一、希尔排序与插入排序 1&#xff09;希尔排序的概念 2&#xff09;插入排序实现 二、希尔排序实现 一、希尔排序与插入排序 1&#xff09;希尔排序的概念 希尔排序(Shells Sort)是插入排序的一种又称“缩小增量排序”&#xff08;Diminishing Incremen…...

测试用例的设计方法(全):判定表驱动分析方法

目录 判定表驱动分析方法 一. 方法简介 二. 实战演习 判定表驱动分析方法 一. 方法简介 1.定义&#xff1a;判定表是分析和表达多逻辑条件下执行不同操作的情况的工具。 2.判定表的优点 能够将复杂的问题按照各种可能的情况全部列举出来&#xff0c;简明并避免遗漏。因此…...

node 第十七天 使用rsa非对称加密 实现前后端加密通信 JSEncrypt和node-rsa

什么是非对称加密 加密过程需要两个钥匙, 公钥和私钥 其中公钥用于加密明文, 私钥用于解密公钥加密的密文, 解密只可以用私钥 公钥和私钥是一对一的关系 公钥可以发送给用户, 不用担心泄露 私钥需要保存在服务端, 不能泄露 例如: 战场上&#xff0c;B要给A传递一条消息&#xf…...

Spring-依赖注入findAutowireCandidates源码实现

findAutowireCandidates()实现 1、找出BeanFactory中类型为type的所有的Bean的名字&#xff0c;根据BeanDefinition就能判断和当前type是不是匹配&#xff0c;不用生成Bean对象 2、把resolvableDependencies中key为type的对象找出来并添加到result中 3、遍历根据type找出的b…...

单页面应用与多页面应用的区别?

单页面应用&#xff08;SPA&#xff09;与多页面应用&#xff08;MPA&#xff09;的主要区别在于页面数量和页面跳转方式。单页面应用只有一个主页&#xff0c;而多页面应用包含多个页面。 单页面应用的优点有&#xff1a; 用户体验好&#xff1a;内容的改变不需要重新加载整…...

模型预处理的ToTensor和Normalize

模型预处理的ToTensor和Normalize flyfish import torch import numpy as np from torchvision import transformsmean (0.485, 0.456, 0.406) std (0.229, 0.224, 0.225)# data0 np.random.randint(0,255,size [4,5,3],dtypeuint8) # data0 data0.astype(np.float64) da…...

nodejs express multer 保存文件名为中文时乱码,问题解决 originalname

nodejs express multer 保存文件名为中文时乱码&#xff0c;问题解决 originalname 一、问题描述 用 express 写了个后台&#xff0c;在接收文件并保存的时候 multer 接收到的文件名为乱码。 二、解决 找了下解决方法&#xff0c;在 github 的 multer issue 中找到了答案 参…...

大数据之LibrA数据库系统告警处理(ALM-12035 恢复任务失败后数据状态未知)

告警解释 执行恢复任务失败后&#xff0c;系统会自动回滚&#xff0c;如果回滚失败&#xff0c;可能会导致数据丢失等问题&#xff0c;如果该情况出现&#xff0c;则上报告警&#xff0c;如果下一次该任务恢复成功&#xff0c;则恢复告警。 告警属性 告警ID 告警级别 可自动…...

汽车生产RFID智能制造设计解决方案与思路

汽车行业需求 汽车行业正面临着快速变革&#xff0c;传统的汽车制造方式正在向柔性化、数字化、自动化和数据化的智能制造体系转变&#xff0c;在这个变革的背景下&#xff0c;汽车制造企业面临着物流、生产、配送和资产管理等方面的挑战&#xff0c;为了应对这些挑战&#xf…...

讲解机器学习中的 K-均值聚类算法及其优缺点。

K-均值聚类算法是一种无监督学习算法&#xff0c;常用于对数据进行聚类分析。其主要步骤如下&#xff1a; 首先随机选择K个中心点&#xff08;质心&#xff09;作为初始聚类中心。 对于每一个样本&#xff0c;计算其与每一个中心点的距离&#xff0c;将其归到距离最近的中心点…...

开源DB-GPT实现连接数据库详细步骤

官方文档&#xff1a;欢迎来到DB-GPT中文文档 — DB-GPT &#x1f44f;&#x1f44f; 0.4.1 第一步&#xff1a;安装Minicoda https://docs.conda.io/en/latest/miniconda.html 第二步&#xff1a;安装Git Git - Downloading Package 第三步&#xff1a;安装embedding 模型到…...

java学习part01

15-Java语言概述-单行注释和多行注释的使用_哔哩哔哩_bilibili 1.命令行 javac编译出class文件 然后java运行 2. java文件每个文件最多一个public类 3.java注释 单行注释 // 多行注释 文档注释 文档注释内容可以被JDK提供的工具javadoc所解析&#xff0c;生成一套以网页文…...

渗透测试学习day3

文章目录 靶机&#xff1a;DancingTask 1Task 2Task 3Task 4Task 5Task 6Task 7Task 8 靶机&#xff1a;RedeemerTask 1Task 2Task 3Task 4Task 5Task 6Task 7Task 8Task 9Task 10Task 11 靶机&#xff1a;AppointmentTask 1Task 2Task 3Task 4Task 5Task 6Task 7Task 8Task 9T…...

【Proteus仿真】【Arduino单片机】数码管显示

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用TM1637、共阳数码管等。 主要功能&#xff1a; 系统运行后&#xff0c;数码管显示数字、字符。 二、软件设计 /* 作者&#xff1a;嗨小易&am…...

【Bug】Python利用matplotlib绘图无法显示中文解决办法

一&#xff0c;问题描述 当利用matplotlib进行图形绘制时&#xff0c;图表标题&#xff0c;坐标轴&#xff0c;标签中文无法显示&#xff0c;显示为方框&#xff0c;并报错 运行窗口报错&#xff1a; 这是中文字体格式未导入的缘故。 二&#xff0c;解决方案 在代码import部…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)

UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中&#xff0c;UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化&#xf…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...

HDFS分布式存储 zookeeper

hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架&#xff0c;允许使用简单的变成模型跨计算机对大型集群进行分布式处理&#xff08;1.海量的数据存储 2.海量数据的计算&#xff09;Hadoop核心组件 hdfs&#xff08;分布式文件存储系统&#xff09;&a…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...