接上一篇,咱們繼續看安全
不知道你們第一次看這段代碼的時候有沒有一臉懵逼,反正我是一臉懵,爲何這個if else 最終都是調用的register0方法,都是同樣的。oop
其實這裏就是爲何Netty是線程安全的根本緣由。學習
咱們先看下 eventLoop.inEventLoop() 方法spa
第一張圖傳入了 當前的 線程, 第二個圖 判斷了 當前這個NioEventLoop中的Thread 是否是和當前線程相等, 若是相等返回true, 相反就是false.線程
咱們debug 看一下debug
發現NioEventLoop中的Thread 當前並無賦值, 值是null,因此返回false.3d
那麼代碼也就進入到了code
這裏其實也容易漏看,其實這裏不僅是啓動一個子線程來執行register0, 其實在這以前還作了好多時間。blog
咱們進入eventLoop的execute()方法,驚喜不。。。隊列
inEventLoop的值確定是false, 而後執行addTask(task),把當前這個任務(register0)加入到隊列中,看下這個隊列
這個隊列是一個LinkedBlockingQueue.
繼續
這就是聞名中外的CAS無鎖技術,固然不瞭解CAS的自行百度。這裏我想說的是,你們能夠學習一下Netty這種寫法。
CAS方式原子性更新state字段的值,這裏的state必定要使用volatile修飾,這個關鍵字不太瞭解的,也自行百度。
回到 startThread() 方法, 先檢查一下Thread 是否已經啓動, 若是沒有啓動,就把state原子性改爲 啓動狀態 ,若是在啓動過程當中出現異常,則再次把state原子性改爲 未啓動狀態。
繼續進入 doStartThread() 方法
先是一個斷言來保證thread必定是null, 而後啓動一個子線程,並把當前這個子線程 賦值給了當前的 這個NioEventLoop 中的 thread 成員變量。 ok ,到如今爲止,NioEventLoop 中的惟一線程肯定。
從這裏咱們進入run() 方法
咱們發現進入到了一個死循環, 而後裏面有一個switch分支,咱們來看下里面的策略計算方法。
在說這個以前咱們再來一塊兒看一個NIO中多路複用器的API
不會阻塞,無論什麼通道就緒都馬上返回(譯者注:此方法執行非阻塞的選擇操做。若是自從前一次選擇操做後,沒有通道變成可選擇的,則此方法直接返回零。)
同時這個方法會清除wakeup()方法的效果。
此方法執行阻塞的 selection operation 。 只有在選擇了至少一個通道以後,纔會返回此選擇器的wakeup
方法,當前線程被中斷,或給定的超時期限到期,以先到者爲準。
此方法不提供實時保證:它調用超時,就像調用Object.wait(long)
方法同樣。
若是另外一個線程在調用select()
或select(long)
方法時被阻止,則該調用將當即返回。 若是當前沒有選擇操做,則下一次調用這些方法之一將當即返回,除非在此期間調用selectNow()
方法。 不管如何,該調用返回的值可能不爲零。 的後續調用select()
點或select(long)
除非此方法在此期間再次調用的方法將阻塞如常。 在兩次連續的選擇操做之間屢次調用此方法與調用它同樣具備相同的效果
好了,瞭解了這些咱們繼續看,
先檢查是否有待處理的task,若是有那麼就非阻塞的檢查一下是否有新的channel被註冊,而後返回channel註冊的數量,多是0, 若是沒有task,則返回 - 1
咱們發現若是有task,那麼這麼switch就直接跳出了。若是返回 - 1 ,就執行 select(wakenUp.getAndSet(false))
咱們先看下沒有task的狀況吧。先大概讀一下這一大段註釋
大概的意思是說:
在調用選擇器喚醒方法,以前,先肯定wakenUp的值,以減小喚醒負載,由於喚醒選擇器是一個耗時的操做。 可是不能把warkup設置true太早,將會觸發競爭。
一、選擇器在wakenUp屬性更新爲false和選擇操做之間被喚醒
二、選擇器在選擇操做和獲取wakenUp屬性之間
在第一種狀況下,當wakenUp屬性更新爲true,接下來的選擇操做就會馬上被喚醒, 直到在下一次循環中wakenUp屬性更新爲false,wakenUp.compareAndSet(false, true) ,將會失敗,同時引發下一次沒必要要的選擇操做阻塞, 怎麼這句話呢(本身的理解)。
咱們看一下這個方法 select(wakenUp.getAndSet(false))
首先咱們假如入參當前是false, 也就是 oldWakenUp = false
那麼再假如當前是有task待處理的,那麼也就是說 hasTasks() && wakenUp.compareAndSet(false, true) == true , 那麼將執行selectNow(), 也就是當前時間到上一次select操做的期間內是否有channel註冊進來。
而後break,接下來
wakeUp 剛剛被CAS 成 true ,因此這裏會執行wakeup操做,也就意味着下一次select操做將會被當即返回。
接下來就是去處理task 和 新接入的客戶端或者讀寫操做了(一會再說這個)。
由於是死循環,咱們繼續回來,又到了
此次的wakeUp 變成了true, 而且把狀態置爲false, 那麼也就是說 oldWakenUp = true
這裏無論有沒有任務,都會當即返回,由於咱們以前執行了selector.wakeup(),這裏我本身猜想多是由於處理讀寫和任務用掉了很長時間,因此這裏直接就檢查當前是會有channel已經註冊進來已經在等待了。
若是有的話,直接break.去執行。
固然若是以前沒有 selector.wakeup() 過,那麼將會執行 1s 的時間,看着1s 內是否有新的channel進來。
繼續看,經過這兩段咱們發現若是循環超時了,那麼將會break掉。
經過這兩段咱們發現,當循環512次以後,那麼將會重建Selector
這裏實際上是由於JDK的BUG致使的,會把CPU飈到100%
整個重建的過程其實就是,建立新的selector,把老的上面的 SelectionKey 都註冊到新的selector上,而後將老的selector關閉掉,具體的內容就不一塊兒看了。