java集合队列完整体系Queue

java集合队列完整体系Queue

    • Collection
      • 1. List
      • 2. Set
      • 3. Queue 队列
        • 3.1 Deque 双端队列
          • 3.1.1 BlockingDeque 同时继承了Deque和BlockingQueue接口
            • 3.1.1.1 LinkedBlockingDeque
          • 3.1.2 LinkedList
          • 3.1.3 ArrayDeque
          • 3.1.4 ConcurrentLinkedDeque
        • 3.2 BlockingQueue 阻塞队列
          • 3.2.1 BlockingDeque 同时继承了Deque和BlockingQueue
            • 3.2.1.1 LinkedBlockingDeque
          • 3.2.2 TransferQueue
            • 接口介绍
            • 3.2.2.1 LinkedTransferQueue
            • 示例1
            • 示例2
          • 3.2.3 ArrayBlockingQueue
          • 3.2.4 DelayQueue
            • 延迟队列使用场景
            • 任务超时处理:订单超时业务:下单之后如果30分钟之内没有付款就自动取消订单。
            • 饿了么订餐通知:下单成功后60S之内给用户发送短信通知。
            • 缓存:缓存中的对象超过空闲时间限制,需要从缓存中移出。
          • 3.2.5 LinkedBlockingQueue
          • 3.2.6 PriorityBlockingQueue
          • 3.2.7 SynchronousQueue
        • 3.3 PriorityQueue 优先级队列
          • 使用优先级队列的自然排序
          • 自定义优先级队列的比较器
          • 使用PriorityQueue处理自定义对象
          • PriorityQueue的使用场景:
        • 3.4 ConcurrentLinkedQueue
      • Java中的队列大致可以分为两类:阻塞队列和非阻塞队列。
        • 阻塞队列:
        • 非阻塞队列:
        • 阻塞队列与非阻塞队列的区别:

Collection

1. List

2. Set

3. Queue 队列

3.1 Deque 双端队列

3.1.1 BlockingDeque 同时继承了Deque和BlockingQueue接口
3.1.1.1 LinkedBlockingDeque
3.1.2 LinkedList
3.1.3 ArrayDeque
3.1.4 ConcurrentLinkedDeque

3.2 BlockingQueue 阻塞队列

3.2.1 BlockingDeque 同时继承了Deque和BlockingQueue
3.2.1.1 LinkedBlockingDeque
3.2.2 TransferQueue
接口介绍

一个生产者可以等待消费者接收元素的阻塞队列。TransferQueue可能在消息传递应用程序中很有用,在这种情况下,生产者有时会(使用transfer方法)等待消费者调用take或poll来接收元素,而在其他时候,生产者会直接将元素入队(使用put方法)而无需等待接收。TransferQueue还提供了非阻塞和超时版本的tryTransfer。TransferQueue还可以查询是否有任何线程正在等待元素,这是对peek操作的反向类比。

与其他阻塞队列一样,TransferQueue可能有容量限制。如果有限制,尝试的传输操作最初可能会阻塞,等待可用空间,以及/或者后续的阻塞等待消费者接收。请注意,在容量为零的队列中,例如SynchronousQueue,put和transfer实际上是等价的。

3.2.2.1 LinkedTransferQueue
示例1

import java.util.concurrent.TransferQueue;
import java.util.concurrent.LinkedTransferQueue;

/**
 * TransferQueue是Java中的一个接口,它是并发包中的一部分,用于在生产者和消费者之间传递元素。
 * TransferQueue是一个生产者可以等待消费者接收元素的阻塞队列
 * TransferQueue提供了几种方法,包括transfer()、tryTransfer()和tryTransfer(long timeout, TimeUnit unit)。
 */
public class TransferQueueTest {
    public static void main(String[] args) {  
        TransferQueue<Integer> queue = new LinkedTransferQueue<>();  
          
        // 生产者线程  
        new Thread(() -> {  
            try {  
                for (int i = 1; i <= 5; i++) {
                    //如果已经有消费者在等待接收该元素(在take或有定时轮询的情况下),则立即转移指定的元素
                    // 否则等待,直到该元素被消费者接收
                    queue.transfer(i);  
                    System.out.println("Produced: " + i);  
                }  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
          
        // 消费者线程  
        new Thread(() -> {  
            try {  
                while (true) {  
                    Integer value = queue.take();  
                    System.out.println("Consumed: " + value);  
                }  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }
    //Consumed: 1
    //Produced: 1
    //Produced: 2
    //Consumed: 2
    //Consumed: 3
    //Produced: 3
    //Produced: 4
    //Consumed: 4
    //Consumed: 5
    //Produced: 5
}
示例2

import java.util.concurrent.TransferQueue;
import java.util.concurrent.LinkedTransferQueue;  
  
public class TransferQueueExample {  
    public static void main(String[] args) {  
        TransferQueue<Integer> queue = new LinkedTransferQueue<>();  
          
        // 生产者线程  
        new Thread(() -> {  
            try {  
                for (int i = 1; i <= 5; i++) {
           //TransferQueue的transfer方法可以将元素立即转移给一个等待中的消费者,如果可能的话。
           // 更准确地说,如果存在一个消费者已经在等待接收元素(在take或定时轮询中),则立即转移指定的元素;
           // 否则返回false,不会将元素入队
                    if (queue.tryTransfer(i)) {  
                        System.out.println("Produced: " + i);  
                    } else {  
                        System.out.println("Failed to produce: " + i);  
                    }  
                }  
            } catch (Exception e) {
                e.printStackTrace();  
            }  
        }).start();  
          
        // 消费者线程  
        new Thread(() -> {  
            try {  
                while (true) {  
                    Integer value = queue.take();  
                    System.out.println("Consumed: " + value);  
                }  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }).start();  
    }
    //Failed to produce: 1
    //Produced: 2
    //Failed to produce: 3
    //Failed to produce: 4
    //Failed to produce: 5
    //Consumed: 2
}
3.2.3 ArrayBlockingQueue
3.2.4 DelayQueue
DelayQueue 是一个无界的 BlockingQueue,
用于放置实现了 Delayed 接口的对象,
存放在DelayQueue 队列中的对象只能在其到期时才能从队列中取走。
实现 Delayed 接口的实现类需要重写 getDelay 和 compareTo 方法。
当生产者线程调用 put 方法添加元素时,
会触发 Delayed 接口中的 compareTo 方法进行排序,
也就是队列中的元素的顺序是按到期时间排序的,而非进入队列的顺序。排在队列头部的元素最早到期,越往后到期时间越晚。
消费者线程查看队列头部的元素,然后调用元素的 getDelay 方法,如果此方法的返回值 ≤0,则消费者线程会从队列中取出此元素处理。
如果 getDealy 方法返回值大于0,则消费者线程 wait 返回的时间值后,再次从队列头部取出元素,此时元素到期可以取出。
也就是说会线程会按照设定的时间进行阻塞。
注意:不能将 null 元素放置到这种队列中。
延迟队列使用场景
任务超时处理:订单超时业务:下单之后如果30分钟之内没有付款就自动取消订单。
饿了么订餐通知:下单成功后60S之内给用户发送短信通知。
import java.util.concurrent.*;

public class ThreadPoolDelayedTaskExecutor {
    /**
     * 使用延迟队列进行存储
     */
    private final DelayQueue<DelayedTask> taskQueue;
    private final ExecutorService taskExecutor;  
    private final ExecutorService taskRetriever;  
  
    public ThreadPoolDelayedTaskExecutor(int taskExecutorPoolSize, int taskRetrieverPoolSize) {  
        this.taskQueue = new DelayQueue<>();  
        this.taskExecutor = Executors.newFixedThreadPool(taskExecutorPoolSize);  
        this.taskRetriever = Executors.newFixedThreadPool(taskRetrieverPoolSize);  
  
        startTaskRetriever();  
    }  
  
    public void execute(Runnable task, long delay) {
        taskQueue.offer(new DelayedTask(task, delay));  
    }  
  
    private void startTaskRetriever() {  
        taskRetriever.execute(() -> {  
            while (true) {  
                try {  
                    Runnable task = taskQueue.take();  
                    taskExecutor.execute(task);  
                } catch (InterruptedException e) {  
                    Thread.currentThread().interrupt();  
                    break;  
                }  
            }  
        });  
    }  
  
    private static class DelayedTask implements Runnable, Delayed {  
        private final Runnable task;  
        private final long startTime;  
  
        public DelayedTask(Runnable task, long delay) {  
            this.task = task;  
            this.startTime = System.currentTimeMillis() + delay;  
        }  
  
        @Override  
        public void run() {  
            task.run();  
        }  
  
        @Override  
        public long getDelay(TimeUnit unit) {  
            long delay = startTime - System.currentTimeMillis();  
            return unit.convert(delay, TimeUnit.MILLISECONDS);  
        }  
  
        @Override  
        public int compareTo(Delayed o) {  
            if (this.startTime < ((DelayedTask) o).startTime) {  
                return -1;  
            } else if (this.startTime > ((DelayedTask) o).startTime) {  
                return 1;  
            } else {  
                return 0;  
            }  
        }  
    }

    public static void main(String[] args) {
        ThreadPoolDelayedTaskExecutor delayedTaskExecutor=new ThreadPoolDelayedTaskExecutor(3,3);
        System.out.println("开始...");
        delayedTaskExecutor.execute(()-> System.out.println("我是小孩也是王"),2000);
        delayedTaskExecutor.execute(()-> System.out.println("我是小孩也是王2"),3000);
        delayedTaskExecutor.execute(()-> System.out.println("我是小孩也是3"),2000);
    }
}
缓存:缓存中的对象超过空闲时间限制,需要从缓存中移出。

可以用DelayQueue保存缓存元素的有效期。使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。这样可以实现缓存的自动清理,有效避免因为缓存过多或者缓存过期而导致的系统压力

import java.util.concurrent.DelayQueue;  
import java.util.concurrent.Delayed;  
import java.util.concurrent.TimeUnit;  
  
public class Cache {  
    private final DelayQueue<DelayedElement> queue;  
  
    public Cache() {  
        queue = new DelayQueue<>();  
    }  
  
    public void put(String key, String value, long expiryTime) {  
        DelayedElement element = new DelayedElement(key, value, expiryTime);  
        queue.put(element);  
    }  
  
    public String get(String key) throws InterruptedException {
    	//一旦能从DelayQueue中获取元素时,表示缓存有效期到了  
        DelayedElement element = queue.take(); // 阻塞直到获取到元素  
        if (element.getKey().equals(key)) {  
            return element.getValue();  
        } else {  
            throw new InterruptedException("Element not found");  
        }  
    }  
  
    private static class DelayedElement implements Delayed {  
        private final String key;  
        private final String value;  
        private final long expiryTime;  
  
        public DelayedElement(String key, String value, long expiryTime) {  
            this.key = key;  
            this.value = value;  
            this.expiryTime = expiryTime;  
        }  
  
        public String getKey() {  
            return key;  
        }  
  
        public String getValue() {  
            return value;  
        }  
  
        @Override  
        public long getDelay(TimeUnit unit) {  
            long delay = expiryTime - System.currentTimeMillis();  
            return unit.convert(delay, TimeUnit.MILLISECONDS);  
        }  
  
        @Override  
        public int compareTo(Delayed o) {  
            if (this.expiryTime < ((DelayedElement) o).expiryTime) {  
                return -1;  
            } else if (this.expiryTime > ((DelayedElement) o).expiryTime) {  
                return 1;  
            } else {  
                return 0;  
            }  
        }  
    }  
}
3.2.5 LinkedBlockingQueue
3.2.6 PriorityBlockingQueue
3.2.7 SynchronousQueue

3.3 PriorityQueue 优先级队列

PriorityQueue是Java中的一个类,它实现了Queue接口。PriorityQueue用于处理元素的队列,这些元素根据其自然排序或者比较器比较的结果排序。PriorityQueue保证队列头部元素总是在最优先的元素。何为最优先?就是根据比较器比较出来的结果排序第一的即为最优先,你可以使用PriorityQueue来实现类似于堆的数据结构。

一个基于优先级堆的无边界优先队列。优先队列中的元素根据它们的自然顺序,或者根据在队列构造时提供的比较器进行排序,这取决于使用的构造函数。优先队列不允许有空元素。依赖于自然排序的优先队列也不允许插入不可比较的对象(这样做可能会导致ClassCastException)。

该队列的头部是按照指定排序的最小的元素。如果多个元素具有相同的最小值,头部将是这些元素之一-关系的打破是任意的。队列检索操作poll、remove、peek和element访问的是队列头部的元素。

优先级队列是无边界的,但是有一个内部容量制约用于存储队列中元素的数组的大小。它总是至少和队列大小一样大。当元素被添加到优先级队列时,其容量会自动增长。增长策略的细节没有具体规定。

这个类及其迭代器实现了Collection和Iterator接口的所有可选方法。在方法iterator()中提供的Iterator和在方法spliterator()中提供的Spliterator并不保证以任何特定的顺序遍历优先级队列的元素。如果需要有序遍历,可以考虑使用Arrays.sort(pq.toArray())。

注意,此实现不是同步的。如果多个线程中的任何一个修改了队列,就不应该同时访问PriorityQueue实例。相反,应该使用线程安全的java.util.concurrent.PriorityBlockingQueue类。

实现注意事项:对于enqueue和dequeue方法(offer、poll、remove()和add),此实现提供O(log n)时间复杂度;对于remove(Object)和contains(Object)方法,线性时间复杂度;对于检索方法(peek、element和size),常数时间复杂度。

使用优先级队列的自然排序

泛型中指定的类要具有可比较性,即要实现Comparable接口,或者指定一个比较器

import java.util.Arrays;
import java.util.PriorityQueue;
/**
在这个示例中,我们创建了一个新的PriorityQueue,并添加了四个元素。
由于PriorityQueue默认使用自然排序,所以这些元素会按照从小到大的顺序排列。
我们打印了队列头部的元素,然后使用poll()方法依次打印了所有元素。
*/
public class PriorityQueueTest {
    public static void main(String[] args) {
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        pq.add(30);
        pq.add(20);
        pq.add(15);
        pq.add(5);

        System.out.println("队列头部元素:" + pq.peek());
        // 在遍历的时候还是会按照先进先出的方式进行遍历
        for (Integer item : pq) {
            System.out.println(item);
        }
        //只有当被移除时,才会查找并移除队列的头部元素
        while (!pq.isEmpty()) {
            //pq.poll();查找并移除队列的头部元素
            System.out.println(pq.poll());
            //打印数组中的元素:
            //说明每次移除后,PriorityQueue只会将队列中最优先的放在前面,其它的保持不变
            //5
            //[15, 30, 20]
            //15
            //[20, 30]
            //20
            //[30]
            //30
            //[]
            System.out.println(Arrays.toString(pq.toArray(new Integer[pq.size()])));
        }
        System.out.println(pq.isEmpty());
    }
自定义优先级队列的比较器
import java.util.PriorityQueue;
/**
 * 在这个示例中,我们创建了一个新的PriorityQueue,并使用了自定义的比较器,
 * 使得元素按照从大到小的顺序排列。然后我们添加了三个元素,并打印了队列头部的元素,最后使用poll()方法依次打印了所有元素。
 */
public class PriorityQueueTest {
    public static void main(String[] args) {
      /*  PriorityQueue pq = new PriorityQueue<>(new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2 - o1;
            }
        });*/

        //使用lambda表达式替换匿名内部类:倒序排优先级
        PriorityQueue<Integer> pq = new PriorityQueue<>((o1, o2) -> o2 - o1);
        pq.add(10);
        pq.add(20);
        pq.add(15);

        System.out.println("队列头部元素:" + pq.peek());

        while (!pq.isEmpty()) {
            System.out.println(pq.poll());
        }
    }
}
使用PriorityQueue处理自定义对象
import java.util.PriorityQueue;
/**
 * 在这个示例中,我们创建了一个名为Student的类,然后创建了一个新的PriorityQueue,用于处理Student对象。
 * 我们使用了自定义的比较器,使得Student对象按照age从大到小的顺序排列。然后我们添加了三个Student对象,并打印了队列头部的元素
 */
class Student {
    int id;
    String name;
    double age;

    public Student(int id, String name, double cgpa) {
        this.id = id;
        this.name = name;
        this.age = cgpa;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("Student{");
        sb.append("id=").append(id);
        sb.append(", name='").append(name).append('\'');
        sb.append(", age=").append(age);
        sb.append('}');
        return sb.toString();
    }
}

public class PriorityQueueTest {
    public static void main(String[] args) {
       /* PriorityQueue pq = new PriorityQueue<>(new Comparator() {
            @Override
            public int compare(Student s1, Student s2) {
                return Double.compare(s2.age, s1.age);
            }
        });*/
        //使用lambda表达式替换匿名内部类:按照学生年龄从大到小排优先级
        PriorityQueue<Student> pq = new PriorityQueue<>((s1, s2) -> Double.compare(s2.age, s1.age));

        pq.add(new Student(1, "张三", 25));
        pq.add(new Student(2, "李四", 19));
        pq.add(new Student(3, "王五", 18));
        pq.add(new Student(3, "赵六", 22));

        System.out.println("队列头部元素:" + pq.peek().name);
        System.out.println("------");
        while (!pq.isEmpty()){
            System.out.println(pq.poll());
        }
        //队列头部元素:张三
        //------
        //Student{id=1, name='张三', age=25.0}
        //Student{id=3, name='赵六', age=22.0}
        //Student{id=2, name='李四', age=19.0}
        //Student{id=3, name='王五', age=18.0}
    }
}
PriorityQueue的使用场景:

任务调度:
在任务调度中,我们需要按照一定的优先级来执行任务。例如,在一个多线程的程序中,我们可以使用PriorityQueue来存储任务,并按照任务的优先级进行排序。当需要执行任务时,只需要从PriorityQueue中取出优先级最高的任务,然后执行即可。

事件驱动:
在事件驱动的程序中,我们需要按照事件的优先级来处理事件。例如,在一个网络服务器中,我们可以使用PriorityQueue来存储网络请求,并按照请求的优先级进行排序。当有新的网络请求到来时,只需要将请求加入PriorityQueue中,然后按照优先级来处理即可。

3.4 ConcurrentLinkedQueue

Java中的队列大致可以分为两类:阻塞队列和非阻塞队列。

阻塞队列:

  1. ArrayBlockingQueue:这是一个基于数组的有界阻塞队列。此队列按照 FIFO(先进先出)的原则对元素进行排序。

  2. LinkedBlockingQueue:这是一个基于链表的阻塞队列,通过构造函数是否有指定队列容量来判断是否有界。

    此队列按照 FIFO(先进先出)的原则对元素进行排序。

  3. PriorityBlockingQueue:这是一个具有优先级的无界阻塞队列。此队列按照元素的自然顺序或者提供的Comparator进行排序。

  4. DelayQueue:这是一个支持延时获取元素的无界阻塞队列。只有在延迟期满时才能从队列中获取元素。

  5. SynchronousQueue:这是一个不存储元素的阻塞队列。每个插入操作必须等待一个相应的删除操作,反之亦然。

  6. LinkedTransferQueue:这是一个基于链表的无界阻塞队列,此队列按照 FIFO(先进先出)的原则对元素进行排序,同时还支持数据的转移操作。

非阻塞队列:

  1. ConcurrentLinkedQueue:这是一个基于链表的无界非阻塞队列,此队列按照 FIFO(先进先出)的原则对元素进行排序。
  2. ArrayDeque:这是一个基于数组的双端队列,具有FIFO(先进先出)的语义。
  3. LinkedList:这是一个基于链表的双端队列,具有FIFO(先进先出)的语义。
  4. PriorityQueue:这是一个具有优先级的无界队列。此队列按照元素的自然顺序或者提供的Comparator进行排序。

阻塞队列与非阻塞队列的区别:

阻塞队列中的阻塞意为等待,与非阻塞队列相比的话有如下的不同点:

入队:

非阻塞队列:当队列满的时候,放入新的元素时,数据丢失。

阻塞队列:当队列满的时候,放入新的元素时,线程阻塞等待,等待队列中有出队的元素,再继续运行放进去。

出队:

非阻塞队列:当队列没有元素的时候,取数据时得到的是null。

阻塞队列:当队列没有元素的时候,取数据时,线程阻塞等待,什么时候有元素入队了,才可以继续运行取出元素。


你可能感兴趣的:(java,J2SE,集合框架,java,Queue,队列,阻塞队列,延时队列,集合)