前记:
这个得首先声明一下,以下大部分内容均参考于:https://blog.csdn.net/wx_vampire/article/details/79585794,本随笔只作为学习作用,侵权删!
说一下我看的学习心得吧!对于BlockingQueue这个接口以及常用的实现类的用法,真的是不看不知道,一看吓一跳!有点超出了我的现有水平的理解范畴了!主要是里面的一些对Java基础中一些不常用的方法,修饰符的使用,这个对我来说真的算是涨姿势了。
还有就是,一些链表在处理数据的算法,这些也让我有点头大,学习的过程中处于“半懂”状态,这让我很受打击,不过好处就是让我知道接下来要往哪方面学习了。
好了,开始正文吧,以下算是一个网上转摘学习资料备份了。
正文:
ArrayBlockingQueue 是数组结构的堵塞队列的一种实现,那么肯定要实现的BlockingQueue接口。
解释一下接口含义
- boolean add(E e); 队列添加元素,返回成功标识,队列满了抛出队列满的异常,无堵塞。
- boolean offer(E e);队列添加元素,返回成功标识,无堵塞。
- void put(E e);队列添加元素,无返回值,队列满了会堵塞。
- boolean offer(E e, long timeout, TimeUnit unit);队列添加元素,队列满了堵塞,设置有超时时间。
- E poll();队列拉取元素,无堵塞,没有值返回null。
- E take();队列拉取元素,队列空了会堵塞,等待能拉取到值为止
- E poll(long timeout, TimeUnit unit);队列拉取元素,队列空了等待,设置有等待超时时间
- E peek() ; 只读队首元素的值,没有返回空
- int remainingCapacity(); 计算剩余容量
- boolean remove(Object o); 移除元素
- int drainTo(Collection<? super E> c, int maxElements); 移除元素放到入参的集合当中
- public Iterator<E> iterator() jdk 1.8以后ArrayBlockingQueue还增加了迭代器功能,这个模块下面会重点介绍,很有意思。
堵塞队列提供的功能:
- 在多线程环境下提供一个类似于生产者和消费者这样的一个模型
- 提供一个FIFO的顺序读取和插入
那就引起我的思考:
- 怎么实现的堵塞机制和堵塞的超时机制?
- 作为一个集合类,数组结构的怎么在多线程环境下实现安全扩容?
- 1.8jdk版本为什么会增加迭代器功能?
下面的代码说明:部分是我自己按照自己的立即翻译,当然多数是参考原作者的注释。
1 package java.util.concurrent; //所在包 2 import java.util.concurrent.locks.Condition; 3 import java.util.concurrent.locks.ReentrantLock;//重入锁 4 import java.util.AbstractQueue;//抽象队列 5 import java.util.Collection;//集合 6 import java.util.Iterator;//迭代器 7 import java.util.NoSuchElementException;//异常 8 import java.lang.ref.WeakReference;//弱引用 9 import java.util.Spliterators;//分割迭代 10 import java.util.Spliterator;//分割迭代 11 class 12 public class ArrayBlockingQueue<E> 13 extends AbstractQueue<E> //继承了抽象队列 14 implements BlockingQueue<E>, //实现了BlockingQueue接口 15 java.io.Serializable//实现了Serializable接口,说明此类可以被序列化 16 { 17 //序列化ID 18 private static final long serialVersionUID = -817911632652898426L; 19 20 /** 阻塞队列中存放的对象 */ 21 default final Object[] items; 22 23 /** 消费者获取对象的下一个对象下标,具体的操作有poll take peek remove */ 24 default int takeIndex 25 26 /** 生产者放入对象的下一个对象的下标,具体的操作有 put offer add */ 27 default int putIndex; 28 29 /** 队列中元素的数量 */ 30 default int count; 31 32 /** 这个锁就是实现生产者,消费者模型的锁模型,并且所有和并发相关的堵塞控制都是通过这个锁来实现的*/ 33 default final ReentrantLock lock; 34 35 /** 这个是有ReentrantLock 中的Condition一个标识队列中有元素非空标志,用于通知消费者队列中有数据了,快来取数据 */ 36 private final Condition notEmpty; 37 38 /** 这个也是ReentrantLock 中的Condition的一个标识,标识队列中的元素不满用于通知生产者队列中空地,快来塞数据*/ 39 private final Condition notFull; 40 41 /** 42 * 这是一个迭代器集合,是之前没有的特性, 43 * 细节:transient 标示变量是序列化忽略这个变量。 44 */ 45 default transient Itrs itrs = null; 46 /***********************【阻塞队列常用的方法】*************************************************************************************/ 47 /** 48 * 49 *堵塞提交,超时返回false 50 */ 51 public boolean offer(E e, long timeout, TimeUnit unit) 52 throws InterruptedException { 53 checkNotNull(e); 54 long nanos = unit.toNanos(timeout); 55 final ReentrantLock lock = this.lock; 56 //获取锁 57 lock.lockInterruptibly(); 58 try { 59 while (count == items.length) { 60 if (nanos <= 0) 61 return false; 62 //这里是使用同步队列器的超时机制,在nanos的时间范围内,方法会在这里堵塞,超过这个时间段nanos的值会被赋值为负数,方法继续,然后在下一个循环返回false。这个标志是未满标志,队列里面未满就可以放进元素嘛。然后判断成功就是一个入队列操作 63 nanos = notFull.awaitNanos(nanos); 64 } 65 enqueue(e); 66 return true; 67 } finally { 68 lock.unlock(); 69 } 70 } 71 /** 72 * 入队列操作,因为putIndex已经是当前该放入元素的下标了,放入元素之后, 73 * 需要将putIndex+1,并且元素数量加1。然后直接调用非空标志通知等待中的消费者 74 * 质疑:如果我没有等待中的消费者,那也要通知,那不是浪费么? 75 * 解释:下端代码是signal的实现 76 public final void signal() { 77 if (!isHeldExclusively()) 78 throw new IllegalMonitorStateException(); 79 Node first = firstWaiter; 80 if (first != null) 81 doSignal(first) 82 } 83 signal方法已经在里面已经对队列的首元素判断空,不通知了, 84 这个引起我的一个思考,确实在函数里面就应该对这些条件做判断要比外面判断更好一些,一个是更健壮,一个是更友好,但是这个最小作用模块还是功能模块,别一个调用链做了多次的这种条件的判断,这就让阅读者难受了。 85 */ 86 private void enqueue(E x) { 87 final Object[] items = this.items; 88 items[putIndex] = x; 89 if (++putIndex == items.length) 90 putIndex = 0; 91 count++; 92 notEmpty.signal(); 93 } 94 /*** 95 * poll的操作和offer基本一样,就是做的是出队列的操作。还有就是一个drainTo方法也很类似,有一个细节有意思就是drainTo是 96 *一个批量操作,但是通知却是一个一个通知的。没有调用singalAll()。因为堵塞队列强调一个顺序。一进一出原则。还有就是在外面判断了有无等待者。因为这**样却是省不必要的循环了。 97 */ 98 public E poll(long timeout, TimeUnit unit) throws InterruptedException { 99 long nanos = unit.toNanos(timeout); 100 final ReentrantLock lock = this.lock; 101 lock.lockInterruptibly(); 102 try { 103 while (count == 0) { 104 if (nanos <= 0) 105 return null; 106 nanos = notEmpty.awaitNanos(nanos); 107 } 108 return dequeue(); 109 } finally { 110 lock.unlock(); 111 } 112 } 113 114 /** 115 * 出队列操作,跟入队列操作正好是相反的,多了一个清理操作 116 * 117 */ 118 private E dequeue() { 119 final Object[] items = this.items; 120 @SuppressWarnings("unchecked") 121 E x = (E) items[takeIndex]; 122 items[takeIndex] = null; 123 if (++takeIndex == items.length) 124 takeIndex = 0; 125 count--; 126 if (itrs != null) 127 //@key jdk1.8的新特性迭代器特性,这里是因为元素的出队列所以清理和这个元素相关联的迭代器 128 itrs.elementDequeued(); 129 //对于生产者的通知 130 notFull.signal(); 131 return x; 132 } 133 /** 134 * 根据下标移除元素,那么会分成两种情况一个是移除的是队首元素,一个是移除的是非队首元素,移除队首元素,就相当于出队*列操作,移除非队首元素那么中间就有空位了,后面元素需要依次补上,然后如果是队尾元素,那么putIndex也就是插入操作的*下标也就需要跟着移动。这里面同样有无用迭代器的清理和notFull标志的通知。elementDequeued 和removedAt 这两个函数差*不多主要做的就是清理。但是不一样的是第一种情况当成出队列来处理了。而第二种就相当于这个元素就没有进过队列来处理,*轻轻地来,轻轻地走不带走一片云彩 135 */ 136 void removeAt(final int removeIndex) { 137 final Object[] items = this.items; 138 //当移除的元素正好是队列首元素,就是take元素,正常的类似出队列的操作, 139 if (removeIndex == takeIndex) { 140 // removing front item; just advance 141 items[takeIndex] = null; 142 if (++takeIndex == items.length) 143 takeIndex = 0; 144 count--; 145 if (itrs != null) 146 itrs.elementDequeued(); 147 // 148 } else { 149 //因为是队列中间的值被移除了,所有后面的元素都要挨个迁移 150 final int putIndex = this.putIndex; 151 for (int i = removeIndex;;) { 152 int next = i + 1; 153 if (next == items.length) 154 next = 0; 155 if (next != putIndex) { 156 items[i] = items[next]; 157 i = next; 158 } else { 159 items[i] = null; 160 this.putIndex = I; 161 break; 162 } 163 } 164 count—; 165 if (itrs != null) 166 itrs.removedAt(removeIndex); 167 } 168 notFull.signal(); 169 } 170 /** 171 * 当元素出队列的时候调用的方法这个出队列方法 172 */ 173 void elementDequeued() { 174 // 在队列为空的时候调用清空所有的迭代器; 175 if (count == 0) 176 queueIsEmpty(); 177 // 当拿元素进行循环的时候,清理所有过期的迭代器 178 else if (takeIndex == 0) 179 takeIndexWrapped(); 180 } 181 } 182 /** 183 * 因为takeIndex等于0了,意味着开始下一个循环了. 184 * 然后通知所有的迭代器,删除无用的迭代器。 185 */ 186 void takeIndexWrapped() { 187 //循环了一次cycle加1 188 cycles++; 189 for (Node o = null, p = head; p != null;) { 190 final Itr it = p.get(); 191 final Node next = p.next; 192 //需要清理的条件,和清理代码 193 if (it == null || it.takeIndexWrapped()) { 194 p.clear(); 195 p.next = null; 196 if (o == null) 197 head = next; 198 else 199 o.next = next; 200 } else { 201 o = p; 202 } 203 p = next; 204 } 205 //没有迭代器了,就关掉迭代器的集合 206 if (head == null) // no more iterators to track 207 itrs = null; 208 } 209 /**这个takeIndexWrapped 是内部类Itr 的方法跟上面不是一个类的方法 210 *这里就是判断这个迭代器所持有的元素还在队列里面么,那么有两个条件,1.isDetached() 211 * 2.就是看这个的循环次数,比建立这个迭代器的时候的循环次数,如果大于1,说明发生过两次以上的循环 212 * 拿里面的元素都换了个遍,拿肯定是不对了,拿这个迭代器就被关闭了。 213 * @return true if this iterator should be unlinked from itrs 214 */ 215 boolean takeIndexWrapped() { 216 // assert lock.getHoldCount() == 1; 217 if (isDetached()) 218 return true; 219 if (itrs.cycles - prevCycles > 1) { 220 // All the elements that existed at the time of the last 221 // operation are gone, so abandon further iteration. 222 shutdown(); 223 return true; 224 } 225 return false; 226 } 227 //将所有的标志位都标记成remove ,null 228 void shutdown() { 229 cursor = NONE; 230 if (nextIndex >= 0) 231 nextIndex = REMOVED; 232 if (lastRet >= 0) { 233 lastRet = REMOVED; 234 lastItem = null; 235 } 236 prevTakeIndex = DETACHED; 237 } 238 /*** 239 * 迭代器的基本方法之一,获取下一个元素,会发生缓存器失效的情况,如果是缓存器失效了,能重组就重组,即从takeIndex开始遍历,如果不行就标记失效, *返回none 240 * @return 241 */ 242 public E next() { 243 // assert lock.getHoldCount() == 0; 244 final E x = nextItem; 245 if (x == null) 246 throw new NoSuchElementException(); 247 final ReentrantLock lock = ArrayBlockingQueue.this.lock; 248 lock.lock(); 249 try { 250 //当判定该迭代器失效了,会重组迭代器,以takeIndex为起点开始遍历,或者标记失效 251 if (!isDetached()) 252 incorporateDequeues(); 253 lastRet = nextIndex; 254 final int cursor = this.cursor; 255 //cursor这个值会在incorporateDequeues方法中修改, 256 if (cursor >= 0) { 257 nextItem = itemAt(nextIndex = cursor); 258 this.cursor = incCursor(cursor); 259 } else { 260 nextIndex = NONE; 261 nextItem = null; 262 } 263 } finally { 264 lock.unlock(); 265 } 266 return x; 267 } 268 269 /** 270 * 发现元素发生移动,通过判定cycle等信息,然后cursor取值游标就重新从takeIndex开始 271 * 下面如果发现所有记录标志的值发生变化,就直接清理本迭代器了。 272 * */ 273 private void incorporateDequeues() { 274 final int cycles = itrs.cycles; 275 final int takeIndex = ArrayBlockingQueue.this.takeIndex; 276 final int prevCycles = this.prevCycles; 277 final int prevTakeIndex = this.prevTakeIndex; 278 if (cycles != prevCycles || takeIndex != prevTakeIndex) { 279 final int len = items.length; 280 // 从本迭代器建立开始,到目前堵塞队列出队列的个数,也就是takeIndex的偏移量 281 long dequeues = (cycles - prevCycles) * len 282 + (takeIndex - prevTakeIndex); 283 // 判断所记录的last,next cursor 还是不是原值如果不是,这个迭代器就判定detach 284 if (invalidated(lastRet, prevTakeIndex, dequeues, len)) 285 lastRet = REMOVED; 286 if (invalidated(nextIndex, prevTakeIndex, dequeues, len)) 287 nextIndex = REMOVED; 288 if (invalidated(cursor, prevTakeIndex, dequeues, len)) 289 cursor = takeIndex; 290 if (cursor < 0 && nextIndex < 0 && lastRet < 0) 291 detach(); 292 else { 293 //重新记录cycle值 294 this.prevCycles = cycles; 295 this.prevTakeIndex = takeIndex; 296 } 297 } 298 } 299 /***********************【迭代器类的链表集合管理类】*************************************************************************************/ 300 /** 301 * 下面是一个内部类:迭代集合链表类(用于处理迭代器) 302 * 作用:管理当前阻塞队列的迭代器 303 */ 304 class Itrs { 305 306 /** 307 * 内部类中的内部类,自定义了一个节点 308 * 将里面的元素设置成弱引用,目标就是当成缓存使用的 309 * WeakReference:帮助JVM合理的释放对象,造成不必要的内存泄漏!! 310 */ 311 private class Node extends WeakReference<Itr> { 312 //下一个节点 313 Node next; 314 //节点构造器 315 Node(Itr iterator, Node next) { 316 super(iterator); 317 this.next = next; 318 } 319 } 320 321 /** 记录循环的次数,当take下标到0的时候为一个循环 cycle+1 */ 322 int cycles = 0; 323 324 /** 定义一个头节点 **/ 325 private Node head; 326 327 /** 用于删除无用的迭代器 */ 328 private Node sweeper = null; 329 /** 330 * 这个标识删除探针 331 */ 332 private static final int SHORT_SWEEP_PROBES = 4; 333 private static final int LONG_SWEEP_PROBES = 16; 334 //迭代器链表集合的构造器 335 Itrs(Itr initial) { 336 register(initial); 337 } 338 /** 339 * 注册逻辑的实现,在链表的最前面加元素 340 */ 341 void register(Itr itr) { 342 head = new Node(itr, head);//创建一个头节点 343 } 344 void doSomeSweeping(boolean tryHarder) {} //删除旧的,过期的,无用的迭代器 345 void takeIndexWrapped() {} // 346 void removedAt(int removedIndex) {} // 347 void queueIsEmpty() {} // 348 void elementDequeued() { } // 349 } 350 /***********************【迭代器类】*************************************************************************************/ 351 /** 352 *创建一个内部类:当前阻塞队列的迭代器 353 */ 354 private class Itr implements Iterator<E> { 355 /** 光标,是迭代器下一次迭代时的坐标,迭代器没有需要遍历的对象了,这个值会为负值*/ 356 private int cursor; 357 358 /** 下一个元素内容,调用Iterator.next方法拿到的值 */ 359 private E nextItem; 360 361 /** 下一个元素的下标,none 是-1 被移除了是-2对应下面的static int */ 362 private int nextIndex; 363 364 /** 上一个元素的内容 */ 365 private E lastItem; 366 367 /** 上一个元素的的下标,none 是-1 被移除的是-2 同样对应下面的static int */ 368 private int lastRet; 369 370 /** 记录之前的开始遍历的下标,当这个迭代器判定为失效了这个值就是DETACHED */ 371 private int prevTakeIndex; 372 373 /* 记录之前循环次数的值,和Cycles进行比对,就知道有没有再循环过 */ 374 private int prevCycles; 375 376 /** 当阻塞队列中无数据时的状态值 */ 377 private static final int NONE = -1; 378 379 /**元素被调用remove方法移走,的状态值*/ 380 private static final int REMOVED = -2; 381 382 /**元素被调用detached方法后的状态值*/ 383 private static final int DETACHED = -3; 384 /**迭代器的初始化函数从takeIndex位置开始遍历*/ 385 Itr() { 386 lastRet = NONE; 387 /**拿到当前阻塞队列的锁*/ 388 final ReentrantLock lock = ArrayBlockingQueue.this.lock; 389 /**开始锁住*/ 390 lock.lock(); 391 try { 392 if (count == 0) {//当前阻塞队列中无数据 393 cursor = NONE;//下一次迭代的索引值:-1 394 nextIndex = NONE;//下一位元素的索引值:-1 395 prevTakeIndex = DETACHED;//上一次遍历使用的索引值:-3 失效 396 } else {//阻塞队列中有数据 397 /** 初始化Itr迭代器的属性值 */ 398 final int takeIndex = ArrayBlockingQueue.this.takeIndex;//拿到当前阻塞队列下一个取到数据的索引值 399 prevTakeIndex = takeIndex;//下一位索引值=前一位取值索引值 400 nextItem = itemAt(nextIndex = takeIndex); 401 cursor = incCursor(takeIndex);//队列首元素后一个 402 if (itrs == null) { 403 itrs = new Itrs(this); 404 } else { 405 //注册到itrs,所有迭代器的集合,顺序注册的 406 itrs.register(this); 407 // 清理无用的迭代器 408 itrs.doSomeSweeping(false); 409 } 410 prevCycles = itrs.cycles; 411 } 412 } finally { 413 //解锁 414 lock.unlock(); 415 } 416 } 417 /**当前迭代器是否失效: 负值意味着失效迭代器 */ 418 boolean isDetached() { 419 return prevTakeIndex < 0; 420 } 421 /**初始化下一个要拿到的元素的索引值 */ 422 private int incCursor(int index) { 423 if (++index == items.length){//如果下一个元素的索引值刚好等于阻塞队列元素个数(说明已经到了队列的尾部),迭代重头开始 424 index = 0;//返回第一个元素的索引值 425 } 426 if (index == putIndex){//下一个元素的索引值=putIndex:生产者放入对象的下一个对象的索引值 427 index = NONE;//NONE=-1 表示队列中无数据 428 } 429 return index; 430 } 431 432 /**如果给定数量的索引无效,则返回true。*/ 433 private boolean invalidated(int index, int prevTakeIndex, 434 long dequeues, int length) { 435 if (index < 0)//队列中无数据,失效 436 return false; 437 int distance = index - prevTakeIndex;//计算 下一位元素的索引值-前一位元素索引值 438 if (distance < 0)//如果差值小于0 439 distance += length; 440 return dequeues > distance; 441 } 442 private void incorporateDequeues() { 443 final int cycles = itrs.cycles; 444 final int takeIndex = ArrayBlockingQueue.this.takeIndex; 445 final int prevCycles = this.prevCycles; 446 final int prevTakeIndex = this.prevTakeIndex; 447 448 if (cycles != prevCycles || takeIndex != prevTakeIndex) { 449 final int len = items.length; 450 long dequeues = (cycles - prevCycles) * len 451 + (takeIndex - prevTakeIndex); 452 453 if (invalidated(lastRet, prevTakeIndex, dequeues, len)) 454 lastRet = REMOVED; 455 if (invalidated(nextIndex, prevTakeIndex, dequeues, len)) 456 nextIndex = REMOVED; 457 if (invalidated(cursor, prevTakeIndex, dequeues, len)) 458 cursor = takeIndex; 459 460 if (cursor < 0 && nextIndex < 0 && lastRet < 0) 461 detach(); 462 else { 463 this.prevCycles = cycles; 464 this.prevTakeIndex = takeIndex; 465 } 466 } 467 } 468 } 469 }
回顾一下;
我介绍了ArrayblockingQueue其实是包含了两个部分一个是标准阻塞队列接口的实现。另一个是jdk1.8增加的迭代器。上一个满大街博客都能找的到,我就把接口描述了一下,然后介绍了两个还算是复杂一点的接口。和整个一个工作原理,没有太多使用case。主要是就是生产者和消费者模型。一个锁应用,和其他的JUC框架不一样。它什么操作都加锁,并发变串行。所以它就没有用到原子类修饰的共享变量。
关于迭代器部分好像是只有我这里有写。如果有百度上有看到相关ArrayBlockingQueue迭代器文章的请留言。毕竟我一家之言,还是有可能会有理解上的偏差。我们总结一下这个迭代器。首先跟别的设计一样,谁用谁new。这个不一样的是会增加一个注册到堵塞队列对象里面itrs上面。然后呢用了一个软引用,那么就GC可以回收避免内存溢出。然后会有对无用的迭代器的清理,类似于threadLocal那样。那么什么是无用的迭代器呢。标识无用就一个条件,我的迭代器标识的结点被覆盖了,因为它空间就这么大,举个例子一个大小5的堵塞队列。然后我建了一个迭代器,那么这个迭代器的下标就是0.然后迭代器我没有马上用,然后进出队列10次,那么之前节点的值已经被替换了。队列里面还有值,但是迭代器的值已经在take方法中被干掉了,已经失效了。判断条件就是cycle的循环次数。有兴趣可以好好了解一下,这应该是我看过的最复杂的迭代器了。
留一些问题:
1.这个迭代器为什么会比arrayList复杂这么多?
2.其实作为堵塞队列来说无非就是数据交换,拿有什么场景是需要迭代器的?而且本身就全都锁控制,效率就不高。还加入这么复杂的迭代模块。会更慢一些的?
这篇文章会看起来比较碎。尽力了。。没有整块的时间去写。而且没想这个迭代器这么复杂。花费我很多时间去研究(没错,这就是我脱稿的原因)
还有就是风格和上一篇不一样了。我希望可以让看这篇文章的人不光是可以学习到之前不知道的知识。也可以触发大家更多的去主动的思考,去思考模块的设计,功能的实现。而不是被动接受这篇文章所传递出来的内容。
还有就是看这种源码。一定要先框架,功能。摸透再去看细节。如果你对这个代码块所要完成的功能不够了解。拿看起来费劲。框架,功能这些都摸透了。再钻到细节上面去。我们可能用到的框架很多,拿要读的源代码那就太多了。其实阅读源代码我觉得是培养一个阅读代码的能力。一个是学习处理这种场景的解决方案,一个是学习编程风格,编码模式。还有就是可能会培养对编程、对探究的兴趣。毕竟工作不能只是为了赚钱。