【基础篇】7 # 队列:队列在线程池等有限资源池中的应用

简介: 【基础篇】7 # 队列:队列在线程池等有限资源池中的应用

说明

【数据结构与算法之美】专栏学习笔记



什么是队列?

队列是一种操作受限的线性表数据结构,特点是先进先出,最基本的操作有:入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头部取一个元素。

915edb3a2681427089343827f3a44015.png



顺序队列和链式队列

  • 用数组实现的队列叫作顺序队列
  • 用链表实现的队列叫作链式队列



基于数组的队列实现方法

队列需要两个指针:

  • 一个是 head 指针,指向队头;
  • 一个是 tail 指针,指向队尾。


用 Java 语言实现:

// 用数组实现的队列
public class ArrayQueue {
  // 数组:items,数组大小:n
  private String[] items;
  private int n = 0;
  // head表示队头下标,tail表示队尾下标
  private int head = 0;
  private int tail = 0;
  // 申请一个大小为capacity的数组
  public ArrayQueue(int capacity) {
    items = new String[capacity];
    n = capacity;
  }
   // 入队操作,将item放入队尾
  public boolean enqueue(String item) {
    // tail == n表示队列末尾没有空间了
    if (tail == n) {
      // tail ==n && head==0,表示整个队列都占满了
      if (head == 0) return false;
      // 数据搬移
      for (int i = head; i < tail; ++i) {
        items[i-head] = items[i];
      }
      // 搬移完之后重新更新head和tail
      tail -= head;
      head = 0;
    }
    items[tail] = item;
    ++tail;
    return true;
  }
  // 出队
  public String dequeue() {
    // 如果head == tail 表示队列为空
    if (head == tail) return null;
    // 为了让其他语言的同学看的更加明确,把--操作放到单独一行来写了
    String ret = items[head];
    ++head;
    return ret;
  }
}


当 tail 移动到最右边,即使数组中还有空闲空间,也无法继续往队列中添加数据,针对这种情况,只需要在入队时,再集中触发一次数据的搬移操作。示意图如下:

fc8cf697c318460fab15cf575c9f0f0c.png


基于链表的队列实现方法


  • 入队时:tail->next= new_nodetail = tail->next
  • 出队时:head = head->next

入队出队示意图:

34915f36420d401fa1fd9db950743106.png

/**
 * 基于链表实现的队列。
 *
 * Author: nameczz
 */
class Node {
    constructor(element) {
        this.element = element
        this.next = null
    }
}
export class QueueBasedOnLinkedList {
    constructor() {
        this.head = null
        this.tail = null
    }
    enqueue(value) {
        if (this.head === null) {
            this.head = new Node(value)
            this.tail = this.head
        } else {
            this.tail.next = new Node(value)
            this.tail = this.tail.next
        }
    }
    dequeue() {
        if (this.head !== null) {
            const value = this.head.element
            this.head = this.head.next
            return value
        } else {
            return -1
        }
    }
}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>07.基于链表实现的队列</title>
    </head>
    <body>
        <script type="module">
            import { QueueBasedOnLinkedList } from './js/07/QueueBasedOnLinkedList.js';
            const newQueue = new QueueBasedOnLinkedList();
            // 元素入队
            newQueue.enqueue(1);
            newQueue.enqueue(2);
            newQueue.enqueue(3);
            console.log('入队--->', newQueue);
            // 获取元素
            let res = 0;
            console.log("-------获取dequeue元素------");
            while (res !== -1) {
                res = newQueue.dequeue();
                console.log(res);
            }
        </script>
    </body>
</html>


d2bd68f753444008b4f086d743a21788.png



循环队列

循环队列就是普通队列首尾相连形成了一个环。

比如:下面队列的大小为 8,当前 head=4,tail=7。


7cdd2e38273c4b0693d3416dbbca94bc.png

当有一个新的元素 a 入队时,放入下标为 7 的位置。将 tail 更新为 0 ,而不是 8。



如何判断循环队列队空和队满呢?

  • 队空:队列为空的判断条件是 head == tail
  • 队满:当队满时,(tail + 1)%n = head

基于数组的循环队列实现方式

public class CircularQueue {
  // 数组:items,数组大小:n
  private String[] items;
  private int n = 0;
  // head表示队头下标,tail表示队尾下标
  private int head = 0;
  private int tail = 0;
  // 申请一个大小为capacity的数组
  public CircularQueue(int capacity) {
    items = new String[capacity];
    n = capacity;
  }
  // 入队
  public boolean enqueue(String item) {
    // 队列满了
    if ((tail + 1) % n == head) return false;
    items[tail] = item;
    // 取余运算保证,数组队列的循环插入效果
    tail = (tail + 1) % n;
    return true;
  }
  // 出队
  public String dequeue() {
    // 如果head == tail 表示队列为空
    if (head == tail) return null;
    String ret = items[head];
    // 因为要保持一个环状,必须通过取余运算才能得到保障!
    head = (head + 1) % n;
    return ret;
  }
}


基于链表实现的循环队列

/**
 * 基于链表实现的循环队列。
 *
 * Author: nameczz
 */
class Node {
    constructor(element) {
        this.element = element
        this.next = null
    }
}
export class CircularQueue {
    constructor() {
        this.head = null
        this.tail = null
    }
  // 入队
    enqueue(value) {
        if (this.head === null) {
            this.head = new Node(value)
            this.head.next = this.head
            this.tail = this.head
        } else {
            const flag = this.head === this.tail
            this.tail.next = new Node(value)
            this.tail.next.next = this.head
            this.tail = this.tail.next
            if (flag) {
                this.head.next = this.tail
            }
        }
    }
  // 出队
    dequeue() {
        if(this.head == null) return -1
        if (this.head === this.tail) {
            const value = this.head.element
            this.head = null
            return value
        } else {
            const value = this.head.element
            this.head = this.head.next
            this.tail.next = this.head
            return value
        } 
    }
    display() {
        let res = 0
        console.log('-------获取dequeue元素------')
        while (res !== -1) {
            res = this.dequeue()
            console.log(res)
        }
    }
}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>07.基于链表实现的循环队列</title>
    </head>
    <body>
        <script type="module">
            import { CircularQueue } from "./js/07/CircularQueue.js";
            const newCircularQueue = new CircularQueue();
            // 插入元素
            newCircularQueue.enqueue(1);
            newCircularQueue.enqueue(2);
            newCircularQueue.enqueue(3);
            console.log(newCircularQueue);
            // 获取元素
            newCircularQueue.display();
            newCircularQueue.enqueue(1);
            newCircularQueue.display();
        </script>
    </body>
</html>

d6554f92db64457ab7d3d98af1463c49.png



阻塞队列


阻塞队列就是在队列基础上增加了阻塞操作。


  • 在队列为空的时候,从队头取数据会被阻塞。直到队列中有了数据才能返回
  • 如果队列已经满了,那么插入数据的操作就会被阻塞。直到队列中有空闲位置后再插入数据,然后再返回


可以使用阻塞队列实现一个生产者 - 消费者模型,有效地协调生产和消费的速度。




如何实现一个线程安全的队列呢?


线程安全的队列叫作并发队列。在多线程情况下,会有多个线程同时操作队列,这个时候就会存在线程安全问题,最简单的解决方式就是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。


如何实现一个高效的并发队列:


   基于数组的循环队列:避免数据搬移

   CAS原子操作:避免真正的去OS底层申请锁资源




队列的应用


基于链表的实现方式,可以实现一个支持无限排队的无界队列(unbounded queue),但是可能会导致过多的请求排队等待,请求处理的响应时间过长。所以,针对响应时间比较敏感的系统,基于链表实现的无限排队的线程池是不合适的。而基于数组实现的有界队列(bounded queue),队列的大小有限,所以线程池中排队的请求超过队列大小时,接下来的请求就会被拒绝,这种方式对响应时间敏感的系统来说,就相对更加合理。


队列可以应用在任何有限资源池中,用于排队请求,比如数据库连接池等。对于大部分资源有限的场景,当没有空闲资源时,基本上都可以通过队列这种数据结构来实现请求排队。







目录
相关文章
|
24天前
|
存储 监控 Java
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
102 12
|
3月前
|
缓存 Java 开发者
Java多线程并发编程:同步机制与实践应用
本文深入探讨Java多线程中的同步机制,分析了多线程并发带来的数据不一致等问题,详细介绍了`synchronized`关键字、`ReentrantLock`显式锁及`ReentrantReadWriteLock`读写锁的应用,结合代码示例展示了如何有效解决竞态条件,提升程序性能与稳定性。
285 6
|
2月前
|
监控 Java 数据库连接
Java线程管理:守护线程与用户线程的区分与应用
在Java多线程编程中,线程可以分为守护线程(Daemon Thread)和用户线程(User Thread)。这两种线程在行为和用途上有着明显的区别,了解它们的差异对于编写高效、稳定的并发程序至关重要。
54 2
|
2月前
|
安全 Java 容器
【JaveEE】——多线程中使用顺序表,队列,哈希表
多线程环境下使用ArrayList(同步机制,写时拷贝),使用队列,哈希表(高频)ConcurrentHashMap(缩小锁粒度,CAS,扩容优化)
|
3月前
|
数据采集 存储 数据处理
Python中的多线程编程及其在数据处理中的应用
本文深入探讨了Python中多线程编程的概念、原理和实现方法,并详细介绍了其在数据处理领域的应用。通过对比单线程与多线程的性能差异,展示了多线程编程在提升程序运行效率方面的显著优势。文章还提供了实际案例,帮助读者更好地理解和掌握多线程编程技术。
|
3月前
|
存储 监控 安全
深入理解ThreadLocal:线程局部变量的机制与应用
在Java的多线程编程中,`ThreadLocal`变量提供了一种线程安全的解决方案,允许每个线程拥有自己的变量副本,从而避免了线程间的数据竞争。本文将深入探讨`ThreadLocal`的工作原理、使用方法以及在实际开发中的应用场景。
107 2
|
3月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
87 7
|
3月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
3月前
|
缓存 安全 C++
C++无锁队列:解锁多线程编程新境界
【10月更文挑战第27天】
142 7
|
3月前
|
消息中间件 存储 安全