首先是個人的一些閱讀原始碼的小技巧,不一定適用每個人,可以直接跳過。
閱讀原始碼的一些個人技巧
部落格+總結
個人覺得大多數情況下跟著一篇優秀的部落格配合著看就足夠了,之後再自己寫部落格總結一遍加深印象,畫一下流程圖基本都能理順。(圖為學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)