其实在你看这篇文章时,自己有许多内容想说,本来我以为自己输出文章就是输出文章而已,但是输出文章的同时,自己的一些行为和想法感觉自己有点不低调了,这种状态太不好了,输出文章确实是自己比较喜欢的一种方式,但是我不想自己高调,既然觉得自己普通平凡,就还以低调的方式做事情。
为什么会有上述情况的一些思考,其实就是因为昨天自己把一篇文章转到了朋友圈,随后删除了,没必要,你输出文章不是为了什么,发朋友圈干嘛,这里不多说了,后面再也不会把自己的文章放到朋友圈了。
其实分析这篇文章的时候自己在想,java这门高级语言真的很好,它的特性也给了自己很多的思考,ArrayBlockingQueue源码分析完之后,我应该不在去分析java的源码了,已经写好的内容,自己会输出和分享出来。
一般工作中我们都是面向业务进行编写代码的,是的,调用javaAPI进行数据操作,所以分析源码算是自己的一个喜好吧,不然我也不会大半夜不睡觉在分析自己喜欢的java语言吧,在18年下半年的时候自己就早已去分析过一部分数据结构了,不过那个时候源码都是都是放到了gitHub上,所以思考仅仅在脑海里停留了那么一段时间,仅此而已,所以后面慢慢把gitHub上面写的内容挪到了公众号的里面,其实技术点掌握没有增加,但是思考和理解的深度有所增加。
这也是最初自己为什么在公众号里面用简单的文字去描述一个技术点,有的人乐于分析spring源码,其它框架的源码,但是我自己不去分析这样的框架,至少目前是,因为我水平达不到,以及我工作中也用不到去分析这样的源码,有的人或许去分析cpu飙高的现象,其实没有无限循环,没有死锁的产生哪有那么多cpu飙高的现象产生,正常的程序不是这样的,换句话说,我自己由于使用锁的不当,确实造成了一点问题,但是不是死锁的问题,所以在之前的文章分析了一下死锁的产生和排查对的文章,这就是自己工作中遇到的一点总结而已,不能说去分析这样的文章不好,我可没说,自己喜欢就好。
这次要分析的ArrayBlockingQueue源码或许是我对之前写的线程池的理解有所帮助一点,主要是为了回顾一下队列的使用,之前好像在分析队列的源码时自己好像说过队列在工作中没有用到过,好像线程池里面用到了队列,自己用过线程池做一些处理业务处理,也算侧面用到了队列吧,打脸。
接下来就是分析ArrayBlockingQueue的源码了,首先自己一般都是从构造函数进行看的,每个人都有自己的一套方法去看的。
public ArrayBlockingQueue(int capacity, boolean fair) { //判断capacity是否小于等于0,若小于等于0则直接抛出异常 if (capacity <= 0) throw new IllegalArgumentException(); //进行内存空间的分配 this.items = new Object[capacity]; //进行锁初始化 lock = new ReentrantLock(fair); //进行两个condition的初始化 notEmpty = lock.newCondition(); notFull = lock.newCondition(); }
队列的offer()方法是比较常用的方法,这是向队列里面添加元素常用的方法,这里就来看下它的方法实现了。
public boolean offer(E e) {//检查添加的元素e是否为空,反正元素e为空是不能添加成功的 checkNotNull(e); final ReentrantLock lock = this.lock;//进行锁的声明 lock.lock();//获取锁 try { //判断队列的元素个数是否已达到队列的容量,若等于则直接返回false if (count == items.length) return false; else { //入队列操作 enqueue(e); return true; } } finally { //当入队列完成,则需要释放锁 lock.unlock(); } }
上面的步骤基本上看完应该是很熟悉这种操作了,接下来就是看下入队列enqueue()方法的操作了。
private void enqueue(E x) {//获取数组的引用 final Object[] items = this.items; //将元素x添加到索引为putIndex位置的数组空间位置 items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++;//队列元素个数加1 notEmpty.signal();//进行通知,这是事件通知机制的一种 }
如何获取队列的元素个数大小,自然会想到size()方法,这里就简单看下size()方法的使用,很简单,就是返回count。
public int size() { final ReentrantLock lock = this.lock;//获取锁 lock.lock();//进行加锁操作 try { return count;//直接返回队列元素个数的count表示 } finally { lock.unlock();//进行释放锁的操作 } }
这里就简单说下,ArrayBlockingQueue是一个线程安全的队列容器,看到这里就知道了吧,它是对每一个方法都进行了加锁操作。
队列操作基本上都是入队和出队操作,所以这里就看下阻塞式的take()方法的分析。
public E take() throws InterruptedException { final ReentrantLock lock = this.lock;//获取引用 lock.lockInterruptibly();//进行可中断锁的加锁操作 try { while (count == 0)//首先判断队列的元素个数是否为0,若为0,则需要等待 notEmpty.await(); return dequeue(); } finally { //进行释放锁的操作,一般都要在这里释放锁 lock.unlock(); } }
接下来就是看下出队的操作,这里就看下dequeue()方法的分析了。
private E dequeue() { final Object[] items = this.items;//获取数组的引用 E x = (E) items[takeIndex];//获取takeIndex位置的元素 items[takeIndex] = null;//然后将item[takeIndex]位置的元素置为null,主要是为了使jvm进行回收 if (++takeIndex == items.length) takeIndex = 0; count--;//队列元素个数减一操作 if (itrs != null) itrs.elementDequeued(); notFull.signal();//进行事件通知的操作 return x;//返回获取的元素x }
如果你仅仅想看看队列的队首元素,但是又不想让元素出队列,所以这里提供了peek()方法,这里很简单,只要我不让jvm进行回收不就行了,即不让获取的元素置为null就可以。
public E peek() { final ReentrantLock lock = this.lock;//获取锁引用 lock.lock();//进行加锁操作 try { return itemAt(takeIndex); //这里看下itemAt()方法 } finally { //进行释放锁操作 lock.unlock(); } }
其实peek()方法的写法很想现在业务里写的这样的模式,获取是大家都参考了业界比较友好的代码写法吧,即方法的共用和嵌套的操作,体会到了吗?
final E itemAt(int i) { return (E) items[i];//这步很简单,就是根据数组下标定位元素位置 }
其实之前在文章中说过,理解了数组就基本上很快速理解这种操作了,这也是数组获取指定位置的元素比较快的原因。
这里再继续看下判断某个元素是否在队列里存在的contains()方法,所以这里就简单的分析下。
public boolean contains(Object o) {//由于队列里面不能存在元素为null的情形,所以这里算是作者写这段代码的一个前置校验//若为null则直接返回false if (o == null) return false; final Object[] items = this.items;//获取数组的引用 final ReentrantLock lock = this.lock;//获取锁的引用 lock.lock();//进行加锁操作 try { //首先判断队列的元素个数是否大于0,队列为空再去判断没有意义了 if (count > 0) { //队列和普通的集合有点区别,就是用了两个"指针"进行队首,队尾的执行,这里自己理解一下 final int putIndex = this.putIndex; int i = takeIndex; do { //判断元素o与队列的每一个元素进行比较,若相等则返回true,跳出while循环,若不相等,则继续判断 //takeIndex是否等于putIndex if (o.equals(items[i])) return true; if (++i == items.length) i = 0; } while (i != putIndex);//这里就采用了do...while的形式进行循环判断 } return false; } finally { //整个过程的操作完成之后就要释放锁操作了,毕竟加锁和释放锁是成对出现的 lock.unlock(); } }
这篇文章是不是有点不同,我把代码的解释说明都放到了代码的后面进行说明,所以文章就不过多的介绍了,写到这说一下,好像自己在哪看过ArrayBlockingQueue代码的介绍了,具体忘了,但是自己看过却没有用过,汗颜,所以这里就自己分析了一下,好像这个是17年还是18年见过的,所以这里就给你们分享一下。
队列的清空操作和普通的清空操作都差不多的意思,这里主要是使队列的元素都置为null,这样就会触发jvm的GC机制进行垃圾元素的回收,所谓的垃圾元素就是对象的引用被释放了的元素,所以理解jvm知识还是有一点用途的,对于理解元素如何回收的,所以这里就简单分享到这里吧,接下来就是分析clear()方法。
public void clear() { final Object[] items = this.items;//获取数组的引用 final ReentrantLock lock = this.lock;//获取锁的引用 lock.lock();//进行加锁操作 try { int k = count;//将队列的元素个数变量count赋值给局部变量k,然后进行判断和操作 if (k > 0) { final int putIndex = this.putIndex;//获取putIndex位置 int i = takeIndex;//获取takeIndex位置 do { items[i] = null; if (++i == items.length) i = 0; } while (i != putIndex);//使用do...while进行循环判断,然后将队列的每一个元素置为null,let's gc takeIndex = putIndex;//将队尾的位置赋值给队首位置 count = 0;//将队列的元素个数置为0 if (itrs != null) itrs.queueIsEmpty(); //下面的这步骤自己后面会单独写个内容进行分析 for (; k > 0 && lock.hasWaiters(notFull); k--) notFull.signal(); } } finally { //整个过程的操作完成之后就需要释放锁操作了,便于下一个线程进行加锁操作 lock.unlock(); } }
之前自己也给你们说过ArrayBlockingQueue的put()方法是一个阻塞性的方法,为什么这么说呢,其实当你看完源码就知道了,所以这里就看下put()方法的源码了。
public void put(E e) throws InterruptedException { checkNotNull(e);//作者的意图就是队列元素不能为null,给我null元素我就报错,所以进行了前置了校验 final ReentrantLock lock = this.lock;//获取锁引用 lock.lockInterruptibly();//进行可中断的加锁操作 try { //看到了吧,这里来了一个while循环,判断队列元素个数是否已经达到了队列的最大容量 //若已经达到了队列的容量,这个时候又没有队列的出队操作,所以就等着吧,等队列有空位置了,你在入队,这样是不是很容易理解 while (count == items.length) notFull.await(); enqueue(e); } finally { //进行释放锁的操作,看到了吧,每个方法都进行了加锁和释放锁的操作 lock.unlock(); } }
分析了put()方法,我觉得给了我一点提示,就是既然队列入队没有null元素的添加,那么再获取队列元素的时候自然不用就判断队列元素是否为null的判空操作了,是不是很容易理解,理解了队列的特点那么就再继续分析下面的方法了。
其实将队列的元素出队列还有一个poll()方法,但是这个方法就有点意思了,如果队列的元素个数为0,则返回null给你,所以当你用这个方法获取队列的元素时就需要判断队列元素是否为空的操作了。
public E poll() { final ReentrantLock lock = this.lock;//进行锁引用的获取 lock.lock();//进行加锁操作 try { //若队列的元素个数为0,则直接返回null,否则进行dequeue()方法的操作了 return (count == 0) ? null : dequeue(); } finally { //进行释放锁的操作 lock.unlock(); } }
关于队列的dequeue()方法上面已经分析过了,所以这里就不再继续分析了,所以这里说下自己为什么不去分析队列的toArray()方法了,其实你看过之前我写的List源码分析的文章后就知道我自己不分析的原因了,这东西用的很少,所以这里给你想分享的与此分析这个方法不如去看下java8的用法,这个真的很好用,自己也分享了两篇关于java8的操作,这可是工作中常用的操作哈。
整个ArrayBlockingQueue的源码分析到这里就结束了,自己或许后面会分享一下关于其它的内容,不再是源码的分析分享了,可能会画点图之类的吧,毕竟这也是自己在这方面需要增进的一点的,"写代码的干不过画ppt"这句话好像是个段子,但是自己想去看下画图这种方式那就去吧,毕竟多了一份思考就需要去了解一下,若没有用自己可以多了解一下外边的内容,若有点用就觉得这件事情还不错,毕竟看到其他画图的都很美,自己也想画画图哈,结束了这篇文章,不知你是否看的过瘾?