茶娱饭后(三):自己动手实现blockQueue的思考

开篇先解释一下队列:

数据结构分为线性数据和非先性数据

这里的线性数据结构指的是内存地址的线性存储,而下面要说的数组实现的队列就是线程地址存储结构中的一种,我们往下看

队列为先进先出的数据结构(FIFO),很多场景下我们会用到例如排队买票,消息队列,固定窗口付款,等等类似”供过于求“的场景。

我们这里以数组实现的案例,自己走一边流程,理解阻塞队列的奥秘

笔者模仿JDK的ArrayBlockQueue实现类类似的代码,因为要对逻辑进行分析,就只保留了核心代码如下:

package org.palm.hazelcast.laohanexcise;

import java.io.Serializable;
import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @date 19-9-18
 * @auther jackliang
 * @description TODO
 */
public class ArrayQueue<E> extends AbstractQueue<E>
        implements BlockingQueue<E>, Serializable {


    private Object[] items;
    
    /*类似C 语言的指针,如*putindex,这里可以意为指向要入库元素索引的指针,具体值为该指针地址所指向的值(item[putIndex])*/ 
    int putIndex  ;
    /* 类似C 语言的指针,如*takeIndex,这里可以意为指向要出库元素索引的指针,具体值为该指针地址所指向的值(item[putIndex])*/
    int takeIndex  ;

    int count ;
    
    /*固定锁,锁住所有资源操作*/
    final ReentrantLock lock;
    
    /*出库操作条件锁 */
    private final Condition notEmpty;
    /*入库操作条件锁*/
    private final Condition notFull;


    public ArrayQueue(int maxSize, boolean fair) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException();
        }
        items = new Object[maxSize];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull = lock.newCondition();
    }


    private static void checkNotNull(Object v) {
        if (v == null) {
            throw new NullPointerException();
        }
    }


    @Override
    public boolean add(E e) {
        return super.add(e);
    }


    public ArrayQueue(int maxSize) {
        this(maxSize, false);
    }


    @Override
    public Iterator<E> iterator() {
        return new Iterator<E>() {
            @Override
            public boolean hasNext() {
                return false;
            }

            @Override
            public E next() {
                return null;
            }
        };
    }

    @Override
    public int size() {
            return count;
    }

    final E itemAt(int i) {
        return (E) items[i];
    }


    @Override
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        ReentrantLock lock = this.lock;
        lock.lockInterruptibly();//可中断锁
        try {
            while (count == items.length) { //队列已满,锁住当前入库状态线程
                notFull.await();//这里满足条件的suspend操作
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
        checkNotNull(e);
        long time = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            while (count == items.length) {//队列已满,锁住当前入库状态线程
                if (time < 0) {
                  /*  每一次notFull.awaitNanos(time)之后返回等待之后的剩余时间
                    A value less than or equal to zero
     *         indicates that no time remains.*/
                    return false;
                }
                time = notFull.awaitNanos(time);//这里等待指定时间
            }
            enqueue(e);//入队列操作
            return true;
        } finally {
            lock.unlock();
        }


    }

    @Override
    public E take() throws InterruptedException {
        ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            if (count == 0) {
                notEmpty.wait();
            }
            E e = dequeue();
            return e;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public boolean contains(Object o) {
        return super.contains(o);
    }

    private void enqueue(E e) {
        Object[] items = this.items;
        items[putIndex] = e;
        if (++putIndex == items.length) { 
        /*这里有一个指针回环的操作,我们已初始化容量为3的队列为例,该指针加到2的时候, count为3,该指针指向第三个元素的地址
            如果指针再加一,刚好满足上面的判断,那么指针从新指向第一个下标为0的索引地址
            可以总结出来,count 的大小就是*putIndex和*takeIndex所指向下标索引之间相隔的距离*/
            putIndex = 0;
        }
        count++;
        //这里通知阻塞的一个消费者消费
        notEmpty.signal();
    }

    private E dequeue() {
        Object[] items = this.items;
        E x = (E) items[takeIndex];
        items[takeIndex] = null; // help GC
        if (++takeIndex == items.length) {//跟随putIndex的脚步+1,这里很巧妙通过两个指针的移动实现了FIFO的效果
            takeIndex = 0;
        }
        count--;
        /*当count 减至0的时候输出输入两指针重合,这种回环操作很巧秒的实现不管中间删除和插入的操作多么切合,队列始终只需要保证
        count == (putindex - takeIndex) 就可以实现FIFO的效果,这也是这个类设计的精华之所在*/
        /*通知一个阻塞的生产者(take(),put(),add())生产*/
        notFull.signal();
        return x;
    }

    @Override
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        ReentrantLock lock = this.lock;
        long nanos = unit.toNanos(timeout);
        lock.lockInterruptibly();
        try {
            while (count == 0) {//这里为什么不用if要用while呢?可以尝试想想区别,应该好理解。
                if (nanos <= 0) {
                    return null;
                }
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public int remainingCapacity() {
        return items.length - count;
    }

    @Override
    public int drainTo(Collection<? super E> c) {
        return 0;
    }

    @Override
    public int drainTo(Collection<? super E> c, int maxElements) {
        return 0;
    }

    @Override
    public boolean offer(E e) {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            if (count == items.length) { // is fulled
                return false;
            } else {
                enqueue(e);
                return true;
            }
        } finally {
            lock.unlock();
        }

    }



    @Override
    public E poll() {
        ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return count == 0 ? null : dequeue();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public E peek() {
        ReentrantLock lock = this.lock;
        lock.lock();
        try {
            return itemAt(takeIndex);
        } finally {
            lock.unlock();
        }
    }


    public ArrayQueue(int maxSize, boolean fair, Collection<? extends E> c) {
        this(maxSize, fair);
        int i = 0;
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            try {
                for (E e : c) {
                    items[i++] = e;
                }
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new IllegalArgumentException();
            }
            putIndex = i == maxSize ? 0 : i;
        } finally {
            lock.unlock();
        }

    }
}

重点就在与阻塞操作的处理和回环算法的指针处理FIFO逻辑,笔者表示膜拜。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值