队列:队列在线程池等有限资源池中的应用

1.理解队列?
典型队列,先进者先出的结构,是一种操作受限的线性数据结构。
队列类似栈,基本操作也有两个,入列(尾部插入数据)和出列(头部取出数据)
 
2.实现队列的方式
类似栈,也可以使用数组和链表来实现队列,
顺序队列:使用数组来实现队列
// 用数组实现的队列
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;
  }
 
  // 入队
  public boolean enqueue(String item) {
    // 如果 tail == n 表示队列已经满了
    if (tail == n) return false;
    items[tail] = item;
    ++tail;
    return true;
  }
  // 出队
  public String dequeue() {
    // 如果 head == tail 表示队列为空
    if (head == tail) return null;
    // 为了让其他语言的同学看的更加明确,把 -- 操作放到单独一行来写了
    String ret = items[head];
    ++head;
    return ret;
  }
}
队列需要两个指针,一个队头指针head,一个队尾指针tail。当发生入队操作,tail指针后移。发生出队操作,head指针后移。
随着入队出队操作的多次进行,head指针和tail指针都会后移,当tail指针后移到数组末尾,即使数组中有空间,队列也无法进行入队操作。
可以通过数据迁移来实现对于指针的管理,但没有必要每次出队都进行数据迁移,这样做会使出队的复杂度变为O(n)。
可以在入队时判断,如果容量不足则进行一次数据迁移或者动态扩容。
这样做,时间复杂度接近于O(1)。
链式队列: 使用链表来实现队列,也需要两个指针。
一个队头指针head,一个队尾指针tail。
 
循环队列:
使用循环队列可以避免数据迁移。
具体实现中,假设数组有10个长度,当tail=9时,进行入队操作,这个时候tail不进行+1,而是=0。
注意:编写循环队列代码要注意确定好队空和队满的判定条件。
如图中的队满情况,head=4,tail=3,n=8  总结规律可以得到  (tail+1)%n=head
可以看到,循环队列在队满的情况下,tail指针的位置是没有数据的,也就是说,循环队列会浪费一个内存空间。
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;
  }
}
阻塞队列和并发队列:
阻塞队列就是在队列基础上增加了阻塞操作。当队列中没有数据,会先阻塞,等到队列中插入了数据再取出数据并返回。当队满时也是一样,会等到队列中有空位,才插入数据并且返回。
事实上,上述定义就是一个生产者消费者模型。我们可以使用阻塞队列轻松实现一个生产者消费者模型。可以通过调整生产者消费者的数量来优化模型,比如配置多个消费者来应对一个生产者。
并发队列:线程安全的队列
最简单直接的方式实在入队出队方法上加锁,但锁粒度太低会导致并发度降低。
可以使用循环队列,利用cas原子操作实现高效的并发队列,这也是为什么循环队列比链式队列应用更加广泛。
对于大部分资源有限的场景,当没有空闲资源时,基本上都可以使用队列来实现请求排队。
队列的应用非常广泛,特别是一些具有某些额外特性的队列,比如循环队列、阻塞队列、并发队列。它们在很多偏底层的系统、框架、中间件的开发中,起着关键性的作用。比如高性能队列 Disruptor、Linux 环形缓存,都用到了循环并发队列;Java concurrent 并发包利用 ArrayBlockingQueue 来实现公平锁等。
思考:
1.除了线程池这种池结构会用到队列排队请求,你还知道有哪些类似的池结构或者场景中会用到队列的排队请求呢?
消息队列,如kafka等
2.今天讲到并发队列,关于如何实现无锁并发队列,网上有非常多的讨论。对这个问题,你怎么看呢?
使用cas原子操作+数组循环队列
考虑使用CAS实现无锁队列,则在入队前,获取tail位置,入队时比较tail是否发生变化,如果否,则允许入队,反之,本次入队失败。出队则是获取head位置,进行cas。

转载于:https://www.cnblogs.com/wangzepu/p/11369014.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
线程池是一种管理和重复利用线程的方法,能够在多个任务之间轮流使用池中的空闲线程,从而减少系统的开销和资源浪费。对于题目要求的线程池,具体实现如下: 1. 创建两个核心线程,在池中持续运行,以便之后分配任务。 2. 创建一个任务队列,使得任务可以在队列中等待执行。任务队列应该是一个先进先出的队列,当队列中的任务被取出时,应该按照任务到达的时间进行排序。 3. 在队列中最多存储30个任务,在超过30个任务时,新的任务将不能加入队列中,而是等待队列中有空余。 4. 当线程池中的线程处于空闲状态时,线程池应该分配一个任务给该线程,以便其执行任务。如果线程池中不再有空闲线程,任务应该等待,直到有可用线程。 5. 当一个线程执行完任务时,它应该从任务队列中取出下一个任务进行执行。 6. 当线程池中的线程数超过2个核心线程时,多出来的线程应该丢弃,否则会浪费系统资源。 下面是一个简单实现的代码示例: ``` import java.util.concurrent.*; public class ThreadPoolDemo { public static void main(String[] args) { // 线程池核心线程数为2,最大线程数为2,任务队列最大存储数为30 ExecutorService pool = new ThreadPoolExecutor(2, 2, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(30)); // 创建30个任务,加入任务队列 for (int i = 1; i <= 30; i++) { final int index = i; pool.execute(new Runnable() { public void run() { System.out.println("任务" + index + "开始执行"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("任务" + index + "执行完成"); } }); } // 关闭线程池 pool.shutdown(); } } ``` 在本示例中,线程池通过ThreadPoolExecutor实现,核心线程数为2,最大线程数为2,任务队列最大存储数为30。随后,创建30个任务,加入任务队列并执行。每个任务都会sleep 2s,以便在控制台中看到执行顺序。最后,调用ThreadPoolExecutor的shutdown方法关闭线程池

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值