首先是个人的一些阅读源码的小技巧,不一定适用每个人,可以直接跳过。
阅读源码的一些个人技巧
博客+总结
个人觉得大多数情况下跟着一篇优秀的博客配合着看就足够了,之后再自己写博客总结一遍加深印象,画一下流程图基本都能理顺。(图为学AQS时本人画的获取独占锁流程图)
类关系
配合idea看类之间的关系(ctrl+alt+shift+u)的功能也能更好的理解整个项目的整体架构。因为很多源码其实并不是真的复杂,只是为了扩展性优雅简洁等原因建立了大量的接口和抽象类以及各种设计模式,使得项目看起来很庞大很复杂,借助这个功能有利于你排除掉一些你暂时不想去关心的设计逻辑。知道那个部分才是最核心的逻辑,专注于去看核心代码。
多看注释
但是如果你看的博客里面刚好缺少了一部分你想看的内容,而你又找不到资料,需要自己去看,又或者你想看的源码一点点资料都找不到的情况下想去看源码。
这个时候比较有作用的就是注释,源码中的注释看不懂也没关系,放到百度翻译里基本也能理解大概的意思。仔细看完方法或类的注释之后你就理解了接下来这个类大致是在做什么,之后读它的源码会通顺很多,有一些方法或类甚至在你看完注释之后就已经能知道你想看的内容了,已经没有需要继续往下读了。
不仅仅是类或方法的注释文档,方法中代码的注释也很重要,基本上稍微复杂一点点的代码,甚至有时候加个锁,作者都会认认真真的写一行注释解释自己这么做的原因。
适当囫囵吞枣
还有一点是适当忽略一些不重要的细节,这个主要看你想看什么,一般我们看第一遍大多数只是想知道大致的流程是什么样的,所以可以适当忽略并发逻辑和一些方法里的内容(看一眼注释先知道这个方法会做什么的就够了)。第一遍大致知道流程,第二第三遍再去深究细节和并发逻辑等。
善用debug
多用debug,很多时候源码走的路线会和你想象中的有很大不同,你以为会进入这个if,其实他偷偷进了else。
短路机制
经常看到利用短路机制的代码,这里以 AbstractQueuedSynchronizer 的 acquire 方法为例子,只有 tryAcquire 获取锁失败, !tryAcquire 返回 true 时才会执行后面进入阻塞队列并挂起的方法,如果获取锁成功了,根据短路机制自然不会执行入队方法。
// AbstractQueuedSynchronizer.acquire(int arg) if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { selfInterrupt(); }
拆高低位
ReentrantReadWriteLock的这段代码里将AQS的state一分为二给共享锁和独占锁使用,个人觉得这种设计非常巧妙:
// ReentrantReadWriteLock abstract static class Sync extends AbstractQueuedSynchronizer { // 下面这块说的就是将 state 一分为二,高 16 位用于共享模式,低16位用于独占模式 static final int SHARED_SHIFT = 16; static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 取 c 的高 16 位值,代表读锁的获取次数(包括重入) static int sharedCount(int c) { return c >>> SHARED_SHIFT; } // 取 c 的低 16 位值,代表写锁的重入次数,因为写锁是独占模式 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
While(n-- > 0)和while (--n >= 0)
忘记在哪里看到的了,翻了一下浏览记录应该是在Java AIO部分的源码里,这种写法感觉很简洁就记下来了,不过可读性似乎不太高,特别是第一种乍一看还以为是lambda表达式
意思等同于 for (int i = 0; i < n; i++) ,但是 while(n-- > 0) 和 while (--n >= 0) 这种写法会直接改变n的值
xx = null
在很多jdk的源码中我们都可以看到 xx = null // help GC 这样的代码,用来置空引用,帮助jvm完成gc。具体可以了解可达性算法。
这里我们以LinkList为例子:
// LinkList 的方法 private E unlinkFirst(Node<E> f) { final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; }
位移运算符
在很多地方都会使用位移来进行运算,平时写算法题也一样很多人都这么使用,下面以 ArrayList 的 grow 方法为例子,这里通过右移1位使 oldCapacity 变为原来的0.5倍,之后加上它本身得到 newCapacity
// ArrayList.grow(int minCapacity) private void grow(int minCapacity) { // . . . . . . int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity就是1.5倍的oldCapacity // . . . . . . }
以上是我目前的水平所能总结出来的,后续学到其他的会继续更新,如果大家有什么补充的请告诉我
最后惯例附一图:(根本不存在想雇佣我的地方( ´_ゝ`).jpg)