多线程学习-基础(十三)(学习参考·网摘) ArrayBlockingQueue源代碼解析(base jdk 1.8)

前记:

    这个得首先声明一下,以下大部分内容均参考于: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还增加了迭代器功能,这个模块下面会重点介绍,很有意思。

 堵塞队列提供的功能:

  1. 在多线程环境下提供一个类似于生产者和消费者这样的一个模型
  2. 提供一个FIFO的顺序读取和插入

那就引起我的思考: 

    1. 怎么实现的堵塞机制和堵塞的超时机制?
    2. 作为一个集合类,数组结构的怎么在多线程环境下实现安全扩容?
    3. 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.其实作为堵塞队列来说无非就是数据交换,拿有什么场景是需要迭代器的?而且本身就全都锁控制,效率就不高。还加入这么复杂的迭代模块。会更慢一些的?

    这篇文章会看起来比较碎。尽力了。。没有整块的时间去写。而且没想这个迭代器这么复杂。花费我很多时间去研究(没错,这就是我脱稿的原因)

    还有就是风格和上一篇不一样了。我希望可以让看这篇文章的人不光是可以学习到之前不知道的知识。也可以触发大家更多的去主动的思考,去思考模块的设计,功能的实现。而不是被动接受这篇文章所传递出来的内容。

    还有就是看这种源码。一定要先框架,功能。摸透再去看细节。如果你对这个代码块所要完成的功能不够了解。拿看起来费劲。框架,功能这些都摸透了。再钻到细节上面去。我们可能用到的框架很多,拿要读的源代码那就太多了。其实阅读源代码我觉得是培养一个阅读代码的能力。一个是学习处理这种场景的解决方案,一个是学习编程风格,编码模式。还有就是可能会培养对编程、对探究的兴趣。毕竟工作不能只是为了赚钱。

原文地址:https://www.cnblogs.com/newwind/p/8976262.html