我们知道,CPU 资源是有限的,任务的处理速度与线程个数并不是线性正相关。相反,过多的线程反而会导致 CPU 频繁切换,处理性能下降。所以,线程池的大小一般都是综合考虑要处理任务的特点和硬件环境,来事先设置的。
    当我们向固定大小的线程池中请求一个线程时,如果线程池中没有空闲资源了,这个时候线程池如何处理这个请求?是拒绝请求还是排队请求?各种处理策略又是怎么实现的呢?
    实际上,这些问题并不复杂,其底层的数据结构就是我们今天要学的内容,队列(queue)。
    我们知道,栈只支持两个基本操作:入栈 push()和出栈 pop()。
    队列跟栈非常相似,支持的操作也很有限,最基本的操作也是两个:入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头部取一个元素。
    队列的概念很好理解,基本操作也很容易掌握。作为一种非常基础的数据结构,队列的应用也非常广泛,特别是一些具有某些额外特性的队列,比如循环队列、阻塞队列、并发队列。它们在很多偏底层系统、框架、中间件的开发中,起着关键性的作用。比如高性能队列 Disruptor、Linux 环形缓存,都用到了循环并发队列;Java concurrent 并发包利用 ArrayBlockingQueue 来实现公平锁等。

    1. // 用数组实现的队列
    2. public class ArrayQueue {
    3. // 数组:items,数组大小:n
    4. private String[] items;
    5. private int n = 0;
    6. // head表示队头下标,tail表示队尾下标
    7. private int head = 0;
    8. private int tail = 0;
    9. // 申请一个大小为capacity的数组
    10. public ArrayQueue(int capacity) {
    11. items = new String[capacity];
    12. n = capacity;
    13. }
    14. // 入队
    15. public boolean enqueue(String item) {
    16. // 如果tail == n 表示队列已经满了
    17. if (tail == n) return false;
    18. items[tail] = item;
    19. ++tail;
    20. return true;
    21. }
    22. // 出队
    23. public String dequeue() {
    24. // 如果head == tail 表示队列为空
    25. if (head == tail) return null;
    26. // 为了让其他语言的同学看的更加明确,把--操作放到单独一行来写了
    27. String ret = items[head];
    28. ++head;
    29. return ret;
    30. }
    31. }

    image.png

    
       // 入队操作,将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 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;
      }
    }
    

    队列最大的特点就是先进先出,主要的两个操作是入队和出队。跟栈一样,它既可以用数组来实现,也可以用链表来实现。用数组实现的叫顺序队列,用链表实现的叫链式队列。特别是长得像一个环的循环队列。在数组实现队列的时候,会有数据搬移操作,要想解决数据搬移的问题,我们就需要像环一样的循环队列。
    循环队列是我们这节的重点。要想写出没有 bug 的循环队列实现代码,关键要确定好队空和队满的判定条件,具体的代码你要能写出来。