Java 集合 一 Queue 队列

什么是队列

队列是数据结构中比较重要的一种类型,它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),跟我们生活中的排队类似。

Queue接口与List、Set同一级别,都是继承了Collection接口。LinkedList实现了Deque接 口。

Queue-队列数据实现。分为阻塞队列和非阻塞队列,下列蓝色区快,为阻塞队列持有方法

灰色部分的持有方法如果失败会直接抛出异常

非阻塞队列

offer: (非阻塞)将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回 true,如果当前没有可用的空间,则返回 false,不会抛异常,add()方法队列已满,则抛出一个IIIegaISlabEepeplian异常:

poll:是从队列中删除第一个元素,poll() 方法在用空集合调用时只是返回 null;remove()方法功能类似,集合为空,则抛出一个NoSuchElementException异常

peek:用于在队列的头部查询元素,元素不会出队列,在队列为空时peek() 返回 null。element()方法功能类似,队列为空,则抛出一个NoSuchElementException异常

阻塞队列

put:将指定的元素插如队列中,等待可用空间,阻塞队列,直到能够有空间插入元素,就是有其他线程调用poll()、take()、remove()方法元素出队列空间释放插入成功,不成功则一直等待,底层实现如下代码

public void put(E e) throws InterruptedException {
    checkNotNull(e);
    // 获取锁
    final ReentrantLock lock = this.lock;
    // 性质是任人摆布(获得锁,优先响应中断)
    lock.lockInterruptibly();
    try {
        // 判断如果元素的个数和当前数组的长度相同,说明没有空间了
        // 这里会作自旋线程挂起,防止线程伪唤醒
        while (count == items.length)
            // 线程挂起
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

take:获取并移除此队列的头部,在元素变得可用之前一直等待 。queue的长度 == 0 的时候,一直阻塞,底层代码如下

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            //如果队列数量为0,线程一直挂起等待,直到数量大于0,返回队列元素锁释放
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

并发Queue

 在并发的队列上jdk提供了两套实现,一个是以ConcurrentLinkedQueue为代表的高性能队列,一个是以BlockingQueue接口为代表的阻塞队列,无论在那种都继承自Queue。

如图下,继承Queue的主要方法,

ConcurrentLinkedQueue

概念理解

ConcurrentLinkedQueue:是一个适用于高并发场景下的队列,通过无锁的方式,是一个非阻塞队列,不提供阻塞方法,实现了高并发状态下的高性能,通常ConcurrentLinkedQueue性能好于BlockingQueueo它是一个基于链接节点的无界线程安全队列。该队列的元素遵循先进先出的原则。头是最先加入的,尾是最近加入的,该队列不允许null元素。

  poll() offer()两个方法都是用CAS实现,offer()方法使用CAS操作替换链表的尾部入队列的操作,poll()方法,将链表的头部使用CAS操作替换成null出队列;

   /**
     *在此队列的尾部插入指定的元素。
     *由于队列是无界的,此方法永远不会返回{@code false}。
     *
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws NullPointerException if the specified element is null
     */

public boolean offer(E e) {
        checkNotNull(e);
        final Node<E> newNode = new Node<E>(e);

        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            if (q == null) {
                // p is last node
                if (p.casNext(null, newNode)) {
                    // CAC操作成功修改
                    // 为了使e成为这个队列的一个元素,
                    // 让newNode 编程当前 队列元素
                    if (p != t) // 一次跳两个节点
                        casTail(t, newNode);  // 这里失败是可以的
                    return true;
                }
                // 丢失的CAS争用另一个线程;下一步重新读取
            }
            else if (p == q)
                p = (t != (t = tail)) ? t : head;
            else
                // 两次跳跃后检查尾部更新
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

public E poll() {
        restartFromHead:
        for (;;) {
            for (Node<E> h = head, p = h, q;;) {
                E item = p.item;

                if (item != null && p.casItem(item, null)) {
                    // Successful CAS is the linearization point
                    // 从队里中删除元素
                    if (p != h) // 一次跳两个节点
                        updateHead(h, ((q = p.next) != null) ? q : p);
                    return item;
                }
                else if ((q = p.next) == null) {
                    updateHead(h, p);
                    return null;
                }
                else if (p == q)
                    continue restartFromHead;
                else
                    p = q;
            }
        }
    }

实例代码 

public void test03_ConcurrentLinkedQueue(){
        //非阻塞的度列
        ConcurrentLinkedQueue<String> conQueue = new ConcurrentLinkedQueue<>();
        conQueue.offer("a");
        conQueue.offer("b");
        conQueue.offer("c");
        conQueue.offer("d");
        conQueue.offer("e");

        //从头部取出元素,并从队列里删除
        System.out.println("从头部取出元素,并从队列里删除 >> "+conQueue.poll());
        System.out.println("删除后的长度 >> "+conQueue.size());    //4
        System.out.println("取出头部元素 >> "+conQueue.peek());    //b
        System.out.println("长度 >> "+conQueue.size());    //4

        //不会提供阻塞方法
        //conQueue.put("");
        //conQueue.take();
    }

 BlockingQueue接口

ArrayBlockingQueue

 基于数组的阻塞队列实现的一个阻塞式队列,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味着生产和消费不能完全并行,长度是需要定义的,有界也就意味着,它不能够存储无限多数量的对象。所以在创建 ArrayBlockingQueue 时,必须要给它指定一个队列的大小可以指定先讲先出或者先讲后出,也叫有界队列,在很多场合非常适合使用。

  • 先进先出队列(队列头的是最先进队的元素;队列尾的是最后进队的元素)
  • 有界队列(即初始化时指定的容量,就是队列最大的容量,不会出现扩容,容量满,则阻塞进队操作;容量空,则阻塞出队操作)
  • 队列不支持空元素
    /** 队列是Object数组 */
    final Object[] items;

    /** 记录出队列下标 执行 take, poll, peek or remove */
    int takeIndex;

    /** 存入队列下标 执行 put, offer, or add */
    int putIndex;

    /** 队列中的元素数量 */
    int count;

    /*
     * Concurrency control uses the classic two-condition algorithm
     * found in any textbook.
     */

    /** 全局锁  全局共享。都会使用这个锁*/
    final ReentrantLock lock;

    /** 读取队列等待池 */
    private final Condition notEmpty;

    /** 新增队列等待池 */
    private final Condition notFull;

从主要的成员变量可以看出 ArrayBlockingQueue 进队操作采用了加锁的方式保证并发安全。并发执行是同步的,也就是在高并发的场景属于是单线程执行

LinkedBlockingQueue

基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也维持着一个数据缓冲队列〈该队列由一个链表构成),LinkedBlockingQueue之所以能够高效的处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作的完全并行运行,他是一个无界队列。不存在线程安全问题,并发高于ArrayBlockingQueue,

// 创建一个 核心线程数量为5,最大数量为10,等待队列最大是3 的线程池,也就是最大容纳13个任务。
// 默认的策略是抛出RejectedExecutionException异常,java.util.concurrent.ThreadPoolExecutor.AbortPolicy
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3), new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println("有任务被拒绝执行了");
            }
        });

常用做线程池实现

    /** 队列最大值,如果没有定义就是int最大值 */
    private final int capacity;

    /** 当前元素数 数量,原子操作线程安全 */
    private final AtomicInteger count = new AtomicInteger();

    /**
     * 定义链表头部
     * Invariant: head.item == null
     */
    transient Node<E> head;

    /**
     * 链表的尾部
     * Invariant: last.next == null
     */
    private transient Node<E> last;

    /** 读锁 */
    private final ReentrantLock takeLock = new ReentrantLock();

    /** 读取元素等待队列*/
    private final Condition notEmpty = takeLock.newCondition();

    /** 写锁 */
    private final ReentrantLock putLock = new ReentrantLock();

    /** 新增元素等待队列*/
    private final Condition notFull = putLock.newCondition();

从类方法成员变量可以看出, LinkedBlockingQueue使用了读写锁互斥锁,读取数据性能更高,链表结构数组

SynchronousQueue同步队列,没有容量的队列

 一种没有缓冲的队列,生产者产生的数据直接会被消费者获取并消费。

  1. take会阻塞,直到取到元素
  2. put时会阻塞,直到被get
  3. 若没有take方法阻塞等待,offer的元素可能会丢失
  4. poll取不到元素,就返回null,如果正好有put被阻塞,可以取到
  5. peek 永远只能取到null,不能让take结束阻塞
  6. 对于一般的队列来说实例化队列后就可以存入,同步队列初始化后容量为零,进入阻塞,当我们去take获取队列才能put进去,可以说入队列和出队列同步执行;
  7. 执行offer(非阻塞)数据可能会丢失,执行获取队列为null;
  8. 使用场景,有一个缓存线程池,用的就是同步队列实现
/*
1、take会阻塞,直到取到元素
2、put时会阻塞,直到被get


3、若没有take方法阻塞等待,offer的元素可能会丢失
4、poll取不到元素,就返回null,如果正好有put被阻塞,可以取到
5、peek 永远只能取到null,不能让take结束阻塞

 */

public class Demo2_SyncQueueTest {
    static SynchronousQueue<String> syncQueue = new SynchronousQueue<>();
    //put时会阻塞,直到被get
    public static void test01() throws InterruptedException {
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(3000L);
                    System.out.println(syncQueue.poll());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        System.out.println("begain to put...");
        syncQueue.put("put_element");
        System.out.println("put done...");
    }

    //3、若没有take方法阻塞等待,offer的元素可能会丢失
    public static void test02() throws InterruptedException {
        syncQueue.offer("offered_element");

        System.out.println(syncQueue.poll());
    }

    //4、poll取不到元素,就返回null,如果正好有put被阻塞,可以取到
    public static void test03() throws InterruptedException {
/*        new Thread(){
            @Override
            public void run() {
                try {
                    syncQueue.put("put_element");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();*/

        Thread.sleep(200L);
        Object obj =  syncQueue.poll();
        System.out.println(obj);
    }

    //peek 永远只能取到null,不能让take结束阻塞
    public static void test04() throws InterruptedException {
        new Thread(){
            @Override
            public void run() {
                try {
                    syncQueue.put("put_element");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        Thread.sleep(200L);
        Object obj =  syncQueue.peek();
        System.out.println(obj);
    }


    public static void main(String args[]) throws InterruptedException {
        test02();
    }
}

 PriorityBlockingQueue优先级队列,非先进先出 可以指定优先级

public class Demo4_PriorityBlockingQueue3 {
    public static void main(String args[]){
        PriorityBlockingQueue<Student> queue = new PriorityBlockingQueue<>(5, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                int num1 = o1.age;
                int num2 = o2.age;

                if (num1 > num2)
                    return 1;
                else if (num1 == num2)
                    return 0;
                else
                    return -1;
            }
        });
        queue.put(new Student(10, "enmily"));
        queue.put(new Student(20, "Tony"));
        queue.put(new Student(5, "baby"));

        for (;queue.size() >0;){
            try {
                System.out.println(queue.take().name);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Student{
    public int age;
    public String name;

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

优先级可以指定,实列代码自定义按照学生的年龄进行出队列

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值