0 - 1 构建属于自己的LinkedBlockingQueue

LinkedBlockingQueue介绍

基于链接节点的可选有界阻塞队列。 此队列对元素进行 FIFO(先进先出)排序。 队列的头部是在队列中时间最长的元素。 队列的尾部是在队列中时间最短的元素。 新元素被插入到队列的尾部,队列检索操作获取队列头部的元素。 链接队列通常比基于数组的队列具有更高的吞吐量,但在大多数并发应用程序中性能更不可预测。
可选的容量绑定构造函数参数用作防止过度队列扩展的一种方式。 容量(如果未指定)等于 Integer.MAX_VALUE。 链接节点在每次插入时动态创建,除非这会使队列超出容量。

LinkedBlockingQueue定义

说明:官方源码中使用的是两个条件变量控制阻塞出队入队,本文中使用的是两个信号量对象来控制,注意区别与使用。

    // 静态内部类Node
    static class Node {
        // 节点值
        E item;
        // next域
        Node next;

        // 构造器
        Node(E e) {
            item = e;
        }
    }

    // 信号量,控制队列满入队和队列空出队
    private Semaphore enSemaphore;
    private Semaphore deSemaphore;
    // 队列容量
    private final int capacity;
    // 队列元素个数
    private int count;
    // 队列头节点
    Node head;
    // 队列尾节点
    Node tail;

    /**
     * Mini_LinkedBlockingQueue有参构造器
     *
     * @param capacity 队列容量
     */
    public Mini_LinkedBlockingQueue111(int capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException();
        }
        this.capacity = capacity;
        tail = head = new Node(null);
        enSemaphore = new Semaphore(capacity);
        deSemaphore = new Semaphore(0);
    }

    /**
     * 入队通用方法
     *
     * @param node 待入队节点
     */
    private void enqueue(Node node) throws InterruptedException {
        
    }

    /**
     * 通用出队方法
     *
     * @return 出队节点
     */
    private E dequeue() {
        return null;
    }

    /**
     * 阻塞入队
     *
     * @param e 待入队元素
     */
    public void put(E e) throws InterruptedException {
        
    }

    /**
     * 非阻塞入队
     *
     * @param e 待入队元素
     * @return 是否入队成功
     */
    public boolean offer(E e) throws InterruptedException {
        return false;
    }

    /**
     * 阻塞出队
     *
     * @return 出队元素
     */
    public E take() throws InterruptedException {
        return null;
    }

    /**
     * 非阻塞出队
     *
     * @return 出队元素
     */
    public E poll() throws InterruptedException {
        return null;
    }

方法细节

    /**
     * 入队通用方法
     *
     * @param node 待入队节点
     */
    private void enqueue(Node node) throws InterruptedException {
        // 若第一次添加元素则初始化head与tail
        if (count == 0) {
            head = tail = node;
            count++;
        } else {
            // 尾节点next指向新节点
            tail.next = node;
            // 更新尾节点
            tail = tail.next;
            count++;
        }

    }

    /**
     * 通用出队方法
     *
     * @return 出队节点
     */
    private E dequeue() {
        // 获取头节点
        Node h = head;
        // 保存头节点的下一节点
        Node next = h.next;
        // 原头节点next域指向自己
        h.next = h;
        // 更新头节点
        head = next;
        // 获取待出队节点值保存后置null
        E e = h.item;
        h.item = null;
        count--;
        if (count == 0) {
            head = tail = null;
        }

        return e;
    }
    /**
     * 阻塞入队
     *
     * @param e 待入队元素
     */
    public void put(E e) throws InterruptedException {
        // 入参检查
        if (e == null) {
            throw new NullPointerException();
        }
        Node newNode = new Node<>(e);
        // 信号量检查,若队满则阻塞
        enSemaphore.acquire();
        enqueue(newNode);
        // 唤醒正在等待获取队列空的线程
        deSemaphore.release();
    }

    /**
     * 非阻塞入队
     *
     * @param e 待入队元素
     * @return 是否入队成功
     */
    public boolean offer(E e) throws InterruptedException {
        if (e == null) {
            throw new NullPointerException();
        }
        Node newNode = new Node<>(e);
        // 若队满则直接返回
        if (count == capacity) {
            return false;
        } else {
            // 队未满则入队
            enqueue(newNode);
            // 信号量变更
            enSemaphore.acquire();
            deSemaphore.release();
            return true;
        }
    }
    /**
     * 阻塞出队
     *
     * @return 出队元素
     */
    public E take() throws InterruptedException {
        // 信号量变更,队列空则阻塞
        deSemaphore.acquire();
        enSemaphore.release();
        E e = dequeue();

        return e;
    }

    /**
     * 非阻塞出队
     *
     * @return 出队元素
     */
    public E poll() throws InterruptedException {
        // 若队列空则返回null
        if (count == 0) {
            return null;
        } else {
            // 否则返回出队元素
            E e = dequeue();
            // 信号量变更
            deSemaphore.acquire();
            enSemaphore.release();
            return e;
        }
    }

测试案例

    private void print(Mini_LinkedBlockingQueue queue) {
        Mini_LinkedBlockingQueue.Node head = queue.head;
        for (int i = 0; i < queue.getCount(); i++) {
            if (head != null && head.next != null) {
                System.out.println(head.item + " --> " + head.next.item);
                head = head.next;
            } else if (head != null) {
                System.out.println(head.item + " --> null");
            }

        }
    }

    @Test
    public void put() throws InterruptedException {
        Mini_LinkedBlockingQueue queue = new Mini_LinkedBlockingQueue<>(3);
        queue.put("hello1");
        queue.put("hello2");
        queue.put("hello3");
        print(queue);

        new Thread(() -> {
            try {
                StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                System.out.println(Thread.currentThread().getName() + "线程正在put...");
                queue.put("hello4");
                stopWatch.stop();
                System.out.println("等到poll了,入队成功,花费时间:" + stopWatch.getLastTaskTimeMillis() + "ms");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();
        Thread.sleep(1000);
        new Thread(() -> {
            try {
                queue.poll();
                System.out.println(Thread.currentThread().getName() + "线程poll了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2").start();
        /*
        hello1 --> hello2
        hello2 --> hello3
        hello3 --> null
        t1线程正在put...
        t2线程poll了
        等到poll了,入队成功,花费时间:1000ms
         */
    }

    @Test
    public void offer() throws InterruptedException {
        Mini_LinkedBlockingQueue queue = new Mini_LinkedBlockingQueue<>(3);
        for (int i = 0; i < 4; i++) {
            boolean offer = queue.offer("hello" + (i + 1));
            System.out.println("第" + (i + 1) + "次入队成功了吗?" + offer);
        }
        /*
        第1次入队成功了吗?true
        第2次入队成功了吗?true
        第3次入队成功了吗?true
        第4次入队成功了吗?false
         */
    }

    @Test
    public void take() throws InterruptedException {
        Mini_LinkedBlockingQueue queue = new Mini_LinkedBlockingQueue<>(3);
        new Thread(() -> {
            try {
                StopWatch stopWatch = new StopWatch();
                stopWatch.start();
                System.out.println(Thread.currentThread().getName() + "线程正在take...");
                String take = queue.take();
                stopWatch.stop();
                System.out.println("等到offer了,出队成功,花费时间:" + stopWatch.getLastTaskTimeMillis() + "ms" + " result:" + take);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();
        Thread.sleep(1000);
        new Thread(() -> {
            try {
                queue.offer("hello");
                System.out.println(Thread.currentThread().getName() + "线程offer了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2").start();
        /*
        t1线程正在take...
        t2线程offer了
        等到offer了,出队成功,花费时间:1008ms
         */
    }

    @Test
    public void poll() throws InterruptedException {
        Mini_LinkedBlockingQueue queue = new Mini_LinkedBlockingQueue<>(3);
        queue.put("hello1");
        queue.put("hello2");
        queue.put("hello3");
        for (int i = 0; i < 4; i++) {
            String poll = queue.poll();
            System.out.println("第" + (i + 1) + "次出队" + poll);
        }
        /*
        第1次出队hello1
        第2次出队hello2
        第3次出队hello3
        第4次出队null
         */
    }

总结

在构造这个类的时候还是遇到了许多BUG,还是自己对于链表结构不是很熟悉的缘故。

控制阻塞入队出队还可以参考其他方式:

  • synchronized关键字 + 标志位 + 唤醒
  • 自旋 + yield
  • CyclicBarrier
 
 

你可能感兴趣的:(Java数据结构篇,java,maven,开发语言,java-ee,数据结构)