个人觉得大多数情况下跟着一篇优秀的博客配合着看就足够了,之后再自己写博客总结一遍加深印象,画一下流程图基本都能理顺。(图为学AQS时本人画的获取独占锁流程图)
1|2类关系
配合idea看类之间的关系(ctrl+alt+shift+u)的功能也能更好的理解整个项目的整体架构。因为很多源码其实并不是真的复杂,只是为了扩展性优雅简洁等原因建立了大量的接口和抽象类以及各种设计模式,使得项目看起来很庞大很复杂,借助这个功能有利于你排除掉一些你暂时不想去关心的设计逻辑。知道那个部分才是最核心的逻辑,专注于去看核心代码。
1|3多看注释
但是如果你看的博客里面刚好缺少了一部分你想看的内容,而你又找不到资料,需要自己去看,又或者你想看的源码一点点资料都找不到的情况下想去看源码。
这个时候比较有作用的就是注释,源码中的注释看不懂也没关系,放到百度翻译里基本也能理解大概的意思。仔细看完方法或类的注释之后你就理解了接下来这个类大致是在做什么,之后读它的源码会通顺很多,有一些方法或类甚至在你看完注释之后就已经能知道你想看的内容了,已经没有需要继续往下读了。
不仅仅是类或方法的注释文档,方法中代码的注释也很重要,基本上稍微复杂一点点的代码,甚至有时候加个锁,作者都会认认真真的写一行注释解释自己这么做的原因。
1|4适当囫囵吞枣
还有一点是适当忽略一些不重要的细节,这个主要看你想看什么,一般我们看第一遍大多数只是想知道大致的流程是什么样的,所以可以适当忽略并发逻辑和一些方法里的内容(看一眼注释先知道这个方法会做什么的就够了)。第一遍大致知道流程,第二第三遍再去深究细节和并发逻辑等。
1|5善用debug
多用debug,很多时候源码走的路线会和你想象中的有很大不同,你以为会进入这个if,其实他偷偷进了else。
2|0短路机制
经常看到利用短路机制的代码,这里以
AbstractQueuedSynchronizer 的 acquire 方法为例子,只有 tryAcquire 获取锁失败, !tryAcquire 返回 true 时才会执行后面进入阻塞队列并挂起的方法,如果获取锁成功了,根据短路机制自然不会执行入队方法。
// AbstractQueuedSynchronizer.acquire(intarg)
if(!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) {
selfInterrupt();
}
3|0拆高低位
ReentrantReadWriteLock的这段代码里将AQS的state一分为二给共享锁和独占锁使用,个人觉得这种设计非常巧妙:
// ReentrantReadWriteLock
abstract static class Sync extends AbstractQueuedSynchronizer {
//下面这块说的就是将state一分为二,高16位用于共享模式,低16位用于独占模式
static finalintSHARED_SHIFT =16;
static finalintSHARED_UNIT = (1<< SHARED_SHIFT);
static finalintMAX_COUNT = (1<< SHARED_SHIFT) -1;
static finalintEXCLUSIVE_MASK = (1<< SHARED_SHIFT) -1;
//取 c 的高16位值,代表读锁的获取次数(包括重入)
staticintsharedCount(intc) {returnc >>> SHARED_SHIFT; }
// 取 c 的低16位值,代表写锁的重入次数,因为写锁是独占模式
staticintexclusiveCount(intc) {returnc & EXCLUSIVE_MASK; }
4|0 While(n-- > 0)和while (--n >= 0)
忘记在哪里看到的了,翻了一下浏览记录应该是在Java AIO部分的源码里,这种写法感觉很简洁就记下来了,不过可读性似乎不太高,特别是第一种乍一看还以为是lambda表达式
意思等同于 for (int i = 0; i < n; i++) ,但是 while(n-- > 0) 和 while (--n >= 0) 这种写法会直接改变n的值
5|0xx = null
在很多jdk的源码中我们都可以看到 xx = null // help GC 这样的代码,用来置空引用,帮助jvm完成gc。具体可以了解可达性算法。
这里我们以LinkList为例子:
//LinkList的方法
privateEunlinkFirst(Nodef){
finalEelement=f.item;
finalNodenext=f.next;
f.item=null;
f.next=null;//helpGC
first=next;
if(next==null)
last=null;
else
next.prev=null;
size--;
modCount++;
returnelement;
}
6|0位移运算符
在很多地方都会使用位移来进行运算,平时写算法题也一样很多人都这么使用,下面以 ArrayList 的 grow 方法为例子,这里通过右移1位使 oldCapacity 变为原来的0.5倍,之后加上它本身得到 newCapacity
// ArrayList.grow(intminCapacity)
private void grow(intminCapacity) {
//. . . . . .
intnewCapacity = oldCapacity + (oldCapacity >>1);//newCapacity就是1.5倍的oldCapacity
// . . . . . .
}
而源码带给你的可不仅仅只是这一些,更多的是编码习惯以及这一个技术最底层的运行原理,当出现问题或者需要这个技术的辅助的时候,你可以更好理解这一技术能起到的作用,以及会产生的问题,双方均衡下,在做出决定,这也是为什么现在市面上面试考察源码的东西越来越多的原因。总结就是以下几点
一、解决问题(BUG)
只要是代码,就会有bug,只是说bug的多与少、深与浅罢了。现在大家都喜欢发布、使用开源项目,不同的开源项目社区成熟度、代码质量又会有较大的差异,遇到bug就不足为奇了。
当然,遇到bug肯定是先在网上搜索是否有类似的问题,一般可以在google、Stack Overflow、项目的issues里面有对应的关键词搜索。如果搜不到,那么就只能看源码解决了
二、知其所以然
如果我们需要将一个开源项目用到自己的项目中,那么就必须了解这项项目的优缺点,并深知原理,对部分细节(尤其是项目的优势、feature)进行深入研究。
如果是成熟的开源项目,遇到问题也许能google到很多答案;但如果是一个处于快速发展中的开源项目,多了解其架构、核心原理,也能帮助快速定位问题。
另外,有的项目文档可能不那么丰富,但又不得不使用,那么如何以正确的姿势使用呢?也得参考源码
三、学习
看源码也是一种不错的学习方式(虽然不一定不是最佳的方式),尤其对于比较优秀的开源项目,能让人大开眼界。
即使是出于学习的目的,也是有很多侧重的,比如
学习语言:代码风格、规范、惯用法、高级语法。对于某个语言的新手,找一个熟悉领域的开源项目来深入掌握这门语言,也是一个不错的注意。
学习设计:数据接口、框架、整体架构
学习理论:算法、协议。比如我之前写过的[raft协议][raft],光看论文是很枯燥的,而且算法理论到工程实践还是有一定的差距,这个时候结合开源项目([mongodb][])实现往往更事半功倍。
四、改造
一般来说,我们刚开始仅仅是使用一个开源项目,但随着使用的深入,会发现一些自己需要的功能并没有很好的支持,向项目组提的issues也可能得不到快速的响应,这个时候就要自己开分支,改代码,加功能了。
当然,比较好的是将自己分支比较好的新feature 给原项目提merge request,反哺开源项目,比如阿里的[Blink],而这也是未来在你面试过程中浓墨重彩的一笔
五、借鉴
他山之石可以攻玉,如果有需要重新开始自己造轮子,那么参考一些已有的、优秀的轮子肯定是有好处的。
当然了,整这么多最后还是为了一份更好的工作,那现在市面上考察的源码知识点都有哪些呢?
源码知识导图
现在市场考察最多的源码主要有以下几点:jdk、spring、mybatis、dubbo、springMVC、Netty
源码解析文档
关注公众号:Java架构师联盟,每日更新技术好文