天天看點

高并發程式設計之Netty高性能調優工具類解析

1.1 多線程共享FastThreadLocal

我們在剖析堆外記憶體配置設定的時候簡單介紹過FastThreadLocal,它類似于JDK的ThreadLocal,也是用于多線程條件下,保證統一線程的對象共享,隻是Netty中定義的FastThreadLocal性能要高于JDK的ThreadLocal,本章開始我們來分析其具體原因。

1.1.1 FastThreadLocal的使用和建立

首先看一個最簡單的Demo。

高并發程式設計之Netty高性能調優工具類解析
高并發程式設計之Netty高性能調優工具類解析

從上面示例中看出,首先聲明一個内部類FastThreadLocalTest繼承FastThreadLocal,并重寫initialValue()方法。initialValue()方法就是用來初始化線程共享對象的。然後聲明一個成員變量fastThreadLocalTest,類型是内部類FastThreadLocalTest,在構造方法中初始化fastThreadLocalTest。main()方法中建立目前類FastThreadLocalDemo的對象fastThreadLocalDemo,然後啟動兩個線程,每個線程通過fastThreadLocalDemo.fastThreadLocalTest.get()方式擷取線程共享對象,因為fastThreadLocalDemo是相同的,是以fastThreadLocalTest對象也是同一個,同一個對象在不同線程中進行get()。第一個線程循環通過set()方法修改共享對象的值,第二個線程則循環判斷fastThreadLocalTest.get()獲得的對象和第一次get()獲得的對象是否相等。這裡輸出結果都是true,說明其他線程雖然不斷修改共享對象的值,但都不影響目前線程共享對象的值,這樣就實作了線程共享對象的功能。

根據上述示例,我們剖析FastThreadLocal的建立,FastThreadLocal的構造方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析

這裡的index代表FastThreadLocal對象的一個下标,每建立一個FastThreadLocal都會有一個唯一的自增的下标,跟進nextVariableIndex()方法。

高并發程式設計之Netty高性能調優工具類解析

上述代碼中,擷取nextIndex通過getAndIncrement()進行原子自增,建立第一個FastThreadLocal對象時,nextIndex為0;建立第二個FastThreadLocal對象時,nextIndex為1;依此類推,第n個FastThreadLocal對象時的nextIndex為n-1,如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

回到Demo中,看線程中的這一句。

高并發程式設計之Netty高性能調優工具類解析

這是調用了FastThreadLocal對象的get()方法,作用是建立一個線程共享對象。get()方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析

這裡調用了一個重載的get()方法,參數中通過InternalThreadLocalMap的get()方法擷取了一個InternalThreadLocalMap對象。我們跟到InternalThreadLocalMap的get()方法中,分析其是如何擷取InternalThreadLocalMap對象的。

高并發程式設計之Netty高性能調優工具類解析

這裡首先擷取目前線程,然後判斷目前線程是否為FastThreadLocalThread線程,通常NioEventLoop線程都是FastThreadLocalThread,用于線程則不是FastThreadLocalThread。

如果是FastThreadLocalThread線程,則調用fastGet()方法擷取InternalThreadLocalMap,從名字上我們能知道,這是一種效率極高的擷取方式。

如果不是FastThreadLocalThread線程,則調用slowGet()方法擷取InternalThreadLocalMap,同樣根據名字,我們知道這是一種效率不太高的擷取方式。

因為我們的Demo并不是EventLoop線程,是以調用slowGet()方法。

首先剖析slowGet()方法。

高并發程式設計之Netty高性能調優工具類解析

通過UnpaddedInternalThreadLocalMap.slowThreadLocalMap擷取一個ThreadLocal對象slowThreadLocalMap,slowThreadLocalMap是UnpaddedInternalThreadLocalMap類的一個靜态屬性,類型是ThreadLocal類型。這裡的ThreadLocal是JDK的ThreadLocal。

然後通過slowThreadLocalMap對象的get()方法,擷取一個InternalThreadLocalMap。如果是第一次擷取,InternalThreadLocalMap有可能是null,則在if塊中建立一個InternalThreadLocalMap對象,并設定在ThreadLocal對象中。

因為Netty實作的FastThreadLocal要比JDK的ThreadLocal快,是以這裡的方法叫作slowGet()方法。

回到InternalThreadLocalMap的get()方法。

高并發程式設計之Netty高性能調優工具類解析

繼續剖析fastGet()方法,通常EventLoop線程建立FastThreadLocalThread線程,是以EventLoop線程執行到這一步的時候會調用fastGet()方法。

高并發程式設計之Netty高性能調優工具類解析

首先FastThreadLocalThread對象直接通過ThreadLocalMap擷取ThreadLocalMap對象。如果ThreadLocalMap為null,則建立一個InternalThreadLocalMap對象設定到FastThreadLocalThread的成員變量中。

我們知道,FastThreadLocalThread對象中維護了一個InternalThreadLocalMap類型的成員變量,可以直接通過threadLocalMap()方法擷取該變量的值,也就是InternalThreadLocalMap。

跟進InternalThreadLocalMap的構造方法。

高并發程式設計之Netty高性能調優工具類解析

這裡調用了父類的構造方法,傳入一個newIndexedVariableTable(),代碼如下。

高并發程式設計之Netty高性能調優工具類解析

這裡建立了一個長度為32的數組,并将數組中的每一個對象都設定為UNSET,UNSET是一個Object的對象,表示該下标的值沒有被設定。

回到InternalThreadLocalMap的構造方法,看其父類的構造方法。

高并發程式設計之Netty高性能調優工具類解析

這裡初始化了一個數組類型的成員變量IndexedVariables,就是newIndexedVariableTable傳回Object的數組。可以知道,每個InternalThreadLocalMap對象中都維護了一個Object類型的數組,那麼這個數組有什麼作用呢?繼續往下剖析。

回到FastThreadLocal的get()方法。

高并發程式設計之Netty高性能調優工具類解析

剖析完InternalThreadLocalMap.get()的相關邏輯,繼續看重載的get()方法。

高并發程式設計之Netty高性能調優工具類解析

首先看這一步。

高并發程式設計之Netty高性能調優工具類解析

這一步是擷取目前Index下标的Object,其實就是擷取每個FastThreadLocal對象綁定的線程共享對象。Index已經分析過,是每一個FastThreadLocal的唯一下标。

跟進indexedVariable()方法。

高并發程式設計之Netty高性能調優工具類解析

首先擷取indexedVariables。indexedVariables是InternalThreadLocalMap對象中維護的數組,初始大小是32。然後在return中判斷目前Index是不是小于目前數組的長度,如果小于則擷取目前下标Index的數組元素,否則傳回UNSET,代表沒有設定的對象。

其實每一個FastThreadLocal對象中所綁定的線程共享對象,都存放在ThreadLocalMap對象中的一個對象數組中,數組中元素的下标對應着FastThreadLocal中的Index屬性,對應關系如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

回到FastThreadLocal重載的get()方法。

高并發程式設計之Netty高性能調優工具類解析

根據以上邏輯,第一次擷取對象v時隻能擷取到UNSET對象,因為該對象并沒有儲存在ThreadLocalMap中的數組IndexedVariables中,是以第一次擷取在if判斷中為false時,會執行到initialize()方法中。跟到initialize()方法中。

高并發程式設計之Netty高性能調優工具類解析

首先調用initialValue()方法,這裡的initialValue實際上調用的是FastThreadLocal子類的重寫initialValue()方法。在Demo中對應這個方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析

通過這個方法會建立一個線程共享對象。通過ThreadLocalMap對象的setIndexedVariable()方法将建立的線程共享對象設定到ThreadLocalMap維護的數組中,參數為FastThreadLocal和建立的對象本身。

setIndexedVariable()方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析

先判斷FastThreadLocal對象的Index是否超過數組IndexedVariables的長度,如果沒有超過,則直接通過下标設定新建立的線程共享對象。通過這個操作,下次擷取該對象的時候就可以直接通過數組下标進行取出。

如果Index超過了數組IndexedVariables的長度,則通過expandIndexedVariableTableAndSet()方法将數組擴容,并且根據Index通過數組下标的方式将線程共享對象設定到數組IndexedVariables中。

以上就是線程共享對象的建立和擷取的過程。

1.1.2 FastThreadLocal的設值

FastThreadLocal的設值是由set()方法完成的,其實就是通過調用set()方法修改線程共享對象,作用域是目前線程,我們回顧上一節Demo中的一個線程set對象的過程。

高并發程式設計之Netty高性能調優工具類解析

set()方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析

首先判斷目前設定的對象是不是UNSET。因為不是UNSET,是以進到if塊中。

if塊調用了重載的set()方法,參數仍然為InternalThreadLocalMap,同時,參數也傳入了set的Value值。

跟進重載的set()方法。

高并發程式設計之Netty高性能調優工具類解析

這裡重點關注if(threadLocalMap.setIndexedVariable(index,value))這部分。通過ThreadLocalMap調用setIndexedVariable()方法進行對象的設定,傳入了目前FastThreadLocal的下标和Value。setIndexedVariable()方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析

這裡的邏輯其實和get非常類似,都是直接通過索引操作的,根據索引值,直接通過數組下标的方式對元素進行設定。

回到FastThreadLocal的set()方法。

高并發程式設計之Netty高性能調優工具類解析

如果修改的對象是UNSET對象,則會調用remove()方法,代碼如下。

高并發程式設計之Netty高性能調優工具類解析

Object v=threadLocalMap.removeIndexedVariable(index)這一步是根據索引Index将值設定成UNSET的。

跟進removeIndexedVariable()方法。

高并發程式設計之Netty高性能調優工具類解析

這裡的邏輯也比較簡單,根據Index通過數組下标的方式将元素設定成UNSET對象。

回到remove()方法中,if(v!=InternalThreadLocalMap.UNSET)判斷如果設定的值不是UNSET對象,則調用onRemoval()方法。

跟進onRemoval()方法。

高并發程式設計之Netty高性能調優工具類解析

它是個空實作,用于交給子類去完成。

1.2 Recycler對象資源回收筒

Recycler我們應該不陌生,因為在前面章節中,有很多地方使用了Recycler。Recycler是Netty實作的一個輕量級對象資源回收筒,很多對象在使用完畢之後,并沒有直接交給GC去處理,而是通過對象資源回收筒将對象回收,目的是為了對象重用和減少GC壓力。比如ByteBuf對象的回收,因為ByteBuf對象在Netty中會被頻繁建立,并且占用比較大的記憶體空間,是以使用完畢後會通過對象資源回收筒的方式進行回收,以達到資源重用的目的。

1.2.1 Recycler的使用和建立

在Netty中,Recycler的使用是相當頻繁的。Recycler的作用是保證對象的循環利用,對象使用完可以通過Recycler回收,需要再次使用則從對象池中取出,不用每次都建立新對象,進而減少對系統資源的占用,同時也減輕了GC的壓力。先看一個示例。

高并發程式設計之Netty高性能調優工具類解析

首先定義了一個Recycler的成員變量RECYCLER,在匿名内部類中重寫了newObject()方法,也就是建立對象的方法,該方法是使用者自定義的。newObject()方法傳回的new User(handle)代表當資源回收筒沒有此類對象的時候,可以通過這種方式建立對象。成員變量RECYCLER可以用來對此類對象回收和再利用。然後定義了一個靜态内部類User,User中有個成員變量Handle,在構造方法中為其指派,Handle的作用是用于對象回收。并且定義了一個方法recycle(),方法中通過handle.recycle(this)這種方式将自身對象進行回收,通過這步操作,可以将對象回收到Recycler中。以上邏輯先做了解,之後會進行詳細分析。

在main()方法中,通過RECYCLER的get()方法擷取一個User,然後進行回收,再通過get()方法将資源回收筒的對象取出,再次進行回收,最後判斷兩次取出的對象是否為同一個對象,最後結果輸出為true。以上Demo就可以說明Recycler的回收再利用的功能。

簡單介紹完Demo,我們就來詳細地分析Recycler的機制。在Recycler的類的源碼中,我們會看到這樣一段邏輯。

高并發程式設計之Netty高性能調優工具類解析
高并發程式設計之Netty高性能調優工具類解析

這一段邏輯用于儲存線程共享對象,而這裡的共享對象,就是一個Stack類型的對象。每個Stack中都維護着一個DefaultHandle類型的數組,用于盛放回收的對象,有關Stack和線程的關系如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

也就是說,在每個Recycler中,都維護着一個線程共享的棧,用于對一類對象的回收。Stack的構造方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析

首先介紹幾個構造方法中初始化的關鍵屬性。

● Parent:表示Recycler對象自身。

● Thread:表示目前Stack綁定的哪個線程。

● maxCapacity:表示目前Stack的最大容量,表示Stack最多能盛放多少個元素。

● elements:表示Stack中存儲的對象,類型為DefaultHandle,可以被外部對象引用,進而實作回收。

● ratioMask:用來控制對象回收的頻率,也就是說每次通過Recycler回收對象的時候,不是每次都會進行回收,而是通過該參數控制回收頻率。

● maxDelayedQueues:稍微有些複雜,很多時候,一個線程建立的對象,有可能會被另一個線程所釋放,而另一個線程釋放的對象不會放在目前線程的Stack中,而是存放在一個叫作WeakOrderQueue的資料結構中,裡面也存放着一個個DefaultHandle,WeakOrderQueue會存放線程1建立且線上程2進行釋放的對象。

現在我們已經知道,maxDelayedQueues屬性的意思就是設定該線程能回收的線程對象的最大值。假設目前線程是線程A,maxDelayedQueues值設定為2,那麼線程A回收了線程B建立的對象,又回收了線程C建立的對象,就不能再回收線程D建立的對象,最多隻能回收2個線程建立的對象。

屬性availableSharedCapacity表示線上程A中建立的對象,在其他線程中緩存的最大個數,同樣,相關邏輯會在之後的内容進行剖析,另外介紹兩個沒有在構造方法中初始化的屬性。

高并發程式設計之Netty高性能調優工具類解析

這裡cursor、prev和head是存放了其他線程的連結清單的指針,用于指向WeakOrderQueue,也是稍作了解,之後會進行詳細剖析。有關Stack異線程之間對象的關系如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

繼續介紹Recycler的構造方法,同時熟悉有關Stack各個參數的預設值。

高并發程式設計之Netty高性能調優工具類解析

這裡調用了重載的構造方法,并傳入了參數DEFAULT_MAX_CAPACITY_PER_THREAD。DEFAULT_MAX_CAPACITY_PER_THREAD的預設值是32768,是在static塊中被初始化的,可以跟進去自行分析。這個值就代表每個線程中Stack中最多回收的元素的個數。繼續跟進重載的構造方法。

高并發程式設計之Netty高性能調優工具類解析

這裡又調用了重載的構造方法,并且傳入32768和MAX_SHARED_CAPACITY_FACTOR。

MAX_SHARED_CAPACITY_FACTOR的預設值是2,同樣在static塊中進行了初始化,有關該屬性的用處稍後講解。繼續跟進構造方法。

高并發程式設計之Netty高性能調優工具類解析

這裡同樣調用了重載的構造方法,傳入了32768和2,還有兩個屬性RATIO和MAX_DELAYED_QUEUES_PER_THREAD。RATIO也在static中被初始化,預設值是8。同上,MAX_DELAYED_QUEUES_PER_THREAD的預設值是2倍CPU核數。繼續跟進構造方法。

高并發程式設計之Netty高性能調優工具類解析

分析Recycler構造方法,主要就是将幾個屬性進行了初始化。

● ratioMask:它是擷取safeFindNextPositivePowerOfTwo()方法的傳回值。在此處safeFindNextPositivePowerOfTwo()方法的傳回值是8,是以ratioMask的最終指派是7。

● maxCapacityPerThread:它是一個大于0的數,如果不滿足判斷條件就進入else代碼塊,最終會被指派為32768。

● 被指派為32768。

● maxSharedCapacityFactor:最終會被指派為2。

● maxDelayedQueuesPerThread:被指派為CPU核數×2。

我們再回到Stack的構造方法。

高并發程式設計之Netty高性能調優工具類解析

根據Recycler初始化屬性的邏輯,可以知道Stack中幾個屬性的值。

● maxCapacity:預設值為32768。

● ratioMask:預設值為7。

● maxDelayedQueues:預設值為CPU核數×2。

● availableSharedCapacity:預設值是32768/2,也就是16384。

1.2.2 從Recycler中擷取對象

回顧上節Demo中的main()方法,從資源回收筒擷取對象。

高并發程式設計之Netty高性能調優工具類解析

通過Recycler的get()方法擷取對象,代碼如下。

高并發程式設計之Netty高性能調優工具類解析
高并發程式設計之Netty高性能調優工具類解析

首先判斷maxCapacityPerThread是否為0,maxCapacityPerThread代表Stack最多能緩存多少個對象,如果緩存0個,說明對象将一個都不會回收。通過調用newObject建立一個對象,并傳入一個NOOP_HANDLE,NOOP_HANDLE是一個Handle,我們看其定義。

高并發程式設計之Netty高性能調優工具類解析

這裡的recycle()方法是一個空實作,代表不進行任何對象回收。回到get()方法中,我們看第二步Stack<T>stack=threadLocal.get(),這裡通過FastThreadLocal對象擷取目前線程的Stack。擷取Stack之後,從Stack中pop出一個Handle,其作用稍後分析。如果取出的對象為null,說明目前資源回收筒内沒有任何對象,通常第一次執行到這裡對象還沒回收,就會是null,這樣則會通過stack.newHandle()建立一個Handle。建立出來的Handle的Value屬性,通過重寫的newObject()方法進行指派,也就是Demo中的User。跟進newHandle()方法。

高并發程式設計之Netty高性能調優工具類解析

這裡建立一個DefaultHandle對象,并傳入this,這裡的this是目前Stack。DefaultHandle的構造方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析

這裡初始化了stack屬性。DefaultHandle中還有一個Value的成員變量。

高并發程式設計之Netty高性能調優工具類解析

這裡的Value用來綁定回收的對象本身。回到get()方法中,分析Handle,我們回到上一步。

高并發程式設計之Netty高性能調優工具類解析

我們分析從Stack中彈出一個Handle的邏輯,跟進pop()方法。

高并發程式設計之Netty高性能調優工具類解析

首先擷取size,size表示目前Stack的對象數。如果size為0,則調用scavenge()方法,這個方法是異線程回收對象的方法,我們放在之後的小節進行分析。如果size大于0,則size進行自減,代表取出一個元素,然後通過size的數組下标的方式将Handle取出,之後将目前下标設定為null,最後将屬性recycleId、lastRecycledId、size進行指派。recycleId和lastRecycledId會在之後的小節進行分析,回到get()方法。

高并發程式設計之Netty高性能調優工具類解析

無論是從Stack中彈出的Handle,還是建立的Handle,最後都要通過handle.value擷取實際使用的對象。

1.2.3 相同線程内的對象回收

上節中剖析了從Recycler中擷取一個對象,本節分析在建立和回收是同線程的前提下,Recycler是如何進行回收的。回顧前面章節Demo中的main()方法。

高并發程式設計之Netty高性能調優工具類解析

這是一個同線程回收對象的典型場景,在一個線程中将對象建立并且回收,我們的User對象定義了recycle方法。

高并發程式設計之Netty高性能調優工具類解析

這裡的recycle是通過Handle對象的recycle()方法實作對象回收的,實際調用的是DefaultHandle的recycle()方法。跟進recycle()方法。

高并發程式設計之Netty高性能調優工具類解析

如果回收的對象為null,則抛出異常。如果不為null,則通過自身綁定Stack的push()方法将自身push到Stack中。push()方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析
高并發程式設計之Netty高性能調優工具類解析

首先判斷目前線程和建立Stack的時候儲存的線程是否是同一線程。如果是,說明是同線程回收對象,則執行pushNow()方法将對象放入Stack中。pushNow()方法的代碼如下。

高并發程式設計之Netty高性能調優工具類解析

如果第一次回收,item.recycleId和item.lastRecycledId都為0,則不會進入if塊。繼續往下看,item.recycleId=item.lastRecycledId=OWN_THREAD_ID這一步将Handle的recycleId和lastRecycledId指派為OWN_THREAD_ID,OWN_THREAD_ID在每一個recycle中都是唯一固定的,這裡我們隻需要記住這個概念就行。然後擷取目前size,如果size超過上限大小,則直接傳回。這裡還有個判斷dropHandle。

高并發程式設計之Netty高性能調優工具類解析

if(!handle.hasBeenRecycled)表示目前對象之前是否沒有被回收過,如果是第一次回收,會傳回true,然後進入if。再看if中的判斷if((++handleRecycleCount&ratioMask)!=0),handleRecycleCount表示目前位置Stack回收了多少次對象(回收了多少次,不代表回收了多少個對象,因為不是每次回收都會被成功地儲存在Stack中),我們之前分析過ratioMask是7,這裡(++handleRecycleCount&ratioMask)!=0表示回收的對象數如果不是8的倍數,則傳回true,表示隻回收1/8的對象,然後将hasBeenRecycled設定為true,表示已經被回收。回到pushNow()方法中,如果size的大小等于Stack中的數組Elements的大小,則将數組Elements進行擴容,最後size通過數組下标的方式将目前Handle設定到Elements的元素中,并将size進行自增。

1.2.4 不同線程間的對象回收

異線程回收對象,就是建立對象和回收對象不在同一條線程的情況下對象回收的邏輯。在1.2.1節簡單介紹過,異線程回收對象,是不會放在目前線程的Stack中的,而是放在一個WeakOrderQueue的資料結構中,回顧我們之前的示意圖如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

相關的邏輯,我們跟到源碼中,首先從回收對象的入口方法開始。DefaultHandle的recycle()方法代碼如下。

高并發程式設計之Netty高性能調優工具類解析

繼續看push()方法的代碼。

高并發程式設計之Netty高性能調優工具類解析

上節分析過,同線程會執行到pushNow(),有關具體邏輯也進行了分析。如果不是同線程,則會執行到pushLater()方法,傳入Handle對象和目前線程對象,跟進pushLater()方法。

高并發程式設計之Netty高性能調優工具類解析

首先通過DELAYED_RECYCLED.get()方法擷取一個delayedRecycled對象,我們看DELAYED_RECYCLED的代碼。

高并發程式設計之Netty高性能調優工具類解析

我們看到DELAYED_RECYCLED是一個FastThreadLocal對象,initialValue()方法建立一個WeakHashMap對象,WeakHashMap是一個Map,Key為Stack,Value為前面提到過的WeakOrderQueue。從中可以分析到,每個線程都維護了一個WeakHashMap對象。WeakHashMap中的元素,是一個Stack和WeakOrderQueue的映射,說明不同的Stack對應不同的WeakOrderQueue。這裡的映射關系可以舉例說明。

比如線程1建立了一個對象,線上程3進行了回收;線程2建立了一個對象,同樣也線上程3進行了回收,那麼線程3對應的WeakHashMap中儲存了兩組關系:線程1對應的Stack和WeakOrderQueue,以及線程2對應的Stack和WeakOrderQueue。我們回到pushLater()方法中,繼續往下看。

高并發程式設計之Netty高性能調優工具類解析

擷取了目前線程的WeakHashMap對象delayedRecycled之後,通過delayedRecycled建立對象的線程的Stack,擷取WeakOrderQueue。這裡的this,就是建立對象的那個線程所屬的Stack,這個Stack是綁定在Handle中的,在建立Handle對象的時候進行的綁定。假設目前線程是線程2,建立Handle的線程是線程1,通過Handle的Stack擷取線程1的WeakOrderQueue。if(queue==null)說明線程2沒有回收過線程1的對象,則進入if塊的邏輯,首先判斷if(delayedRecycled.size()>=maxDelayedQueues)。

● delayedRecycled.size()表示目前線程回收其他建立對象的線程的個數,也就是有幾個其他的線程在目前線程回收對象。

● maxDelayedQueues表示最多能回收的線程個數,如果超過這個值,就表示目前線程不能再回收其他線程的對象了。

通過delayedRecycled.put(this,WeakOrderQueue.DUMMY)辨別建立對象的線程的Stack所對應的WeakOrderQueue不可用,DUMMY可以了解為不可用。如果沒有超過maxDelayedQueues,則通過if判斷中的WeakOrderQueue.allocate(this,thread)方式建立一個WeakOrderQueue。allocate傳入this,也就是建立對象的線程對應的Stack,跟進allocate()方法。

高并發程式設計之Netty高性能調優工具類解析

reserveSpace(stack.availableSharedCapacity,LINK_CAPACITY)表示線程1的Stack還能不能配置設定LINK_CAPACITY個元素,如果可以,則直接通過new的方式建立一個WeakOrderQueue對象。回到reserveSpace()方法。

參數availableSharedCapacity表示線程1的Stack允許外部線程給其緩存多少個對象,之前我們分析過是16384,space預設是16。方法中通過一個CAS操作,将16384減去16,表示Stack可以給其他線程緩存的對象數為16384-16,而這16個元素,将由線程2緩存。

回到pushLater()方法,建立之後通過delayedRecycled.put(this,queue)将Stack和WeakOrderQueue進行關聯,通過queue.add(item)将建立的WeakOrderQueue添加一個Handle。講解WeakOrderQueue之前,先了解下WeakOrderQueue的資料結構。WeakOrderQueue維護了多個Link,Link之間通過連結清單進行連接配接,每個Link可以盛放16個Handle。我們分析過,在reserveSpace()方法中将stack.availableSharedCapacity-16,其實就表示先配置設定16個空間放在Link裡,下次回收的時候,如果這16個空間沒有填滿,則可以繼續往裡盛放。如果16個空間都已填滿,則通過繼續添加Link的方式繼續配置設定16個空間用于盛放Handle。WeakOrderQueue和WeakOrderQueue之間也通過連結清單進行關聯,可以根據下圖了解上述邏輯。

高并發程式設計之Netty高性能調優工具類解析

根據以上思路,我們看WeakOrderQueue的構造方法。

高并發程式設計之Netty高性能調優工具類解析

這裡有個Head和Tail,都指向一個Link對象,其實在WeakOrderQueue中維護了一個連結清單,Head和Tail分别代表頭節點和尾節點,初始狀态下,頭節點和尾節點都指向同一個節點。簡單看下Link的類的定義。

高并發程式設計之Netty高性能調優工具類解析
高并發程式設計之Netty高性能調優工具類解析

每次建立一個Link,都會建立一個DefaultHandle類型的數組用于盛放DefaultHandle對象,預設大小是16個。

readIndex是一個讀指針,之後小節會進行分析。next節點則指向下一個Link。回到WeakOrderQueue的構造方法中,owner是對目前線程進行一個包裝,代表了目前線程。

接下來在一個同步塊中,将目前建立的WeakOrderQueue插入Stack指向的第一個WeakOrderQueue,也就是Stack的Head屬性,指向我們建立的WeakOrderQueue,如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

如果線程2建立一個和Stack關聯的WeakOrderQueue,Stack的頭節點就會指向線程2建立的WeakOrderQueue。如果之後線程3也建立了一個和Stack關聯的WeakOrderQueue,Stack的頭節點就會指向新建立的線程3的WeakOrderQueue,然後線程3的WeakOrderQueue再指向線程2的WeakOrderQueue。也就是無論哪個線程建立一個和同一個Stack關聯的WeakOrderQueue的時候,都插入Stack指向的WeakOrderQueue清單的頭部,這樣就可以将Stack和其他線程釋放對象的容器WeakOrderQueue進行綁定。回到pushLater()方法。

高并發程式設計之Netty高性能調優工具類解析
高并發程式設計之Netty高性能調優工具類解析

根據之前分析的WeakOrderQueue的資料結構,分析最後一步,也就是WeakOrderQueue的add()方法。

高并發程式設計之Netty高性能調優工具類解析

首先看handle.lastRecycledId=id,lastRecycledId表示Handle上次回收的id,而id表示WeakOrderQueue的id,weakOrderQueue每次建立的時候,會自增一個唯一的id。Link tail=this.tail表示擷取目前WeakOrderQueue中指向最後一個Link的指針,也就是尾指針。再看if((writeIndex=tail.get())==LINK_CAPACITY),tail.get()表示擷取目前Link中已經填充元素的個數,如果等于16,說明元素已經填充滿。然後通過reserveSpace()方法判斷目前WeakOrderQueue是否還能緩存Stack的對象,reserveSpace()方法會根據Stack的屬性availableSharedCapacity-16的方式判斷還能否緩存Stack的對象,如果不能再緩存Stack的對象,則傳回。如果還能繼續緩存,則再建立一個Link,并将尾節點指向新建立的Link,并且原來尾節點的next節點指向新建立的Link,然後擷取目前Link的writeIndex,也就是寫指針。如果新建立的Link中沒有元素,writeIndex為0,之後将尾部的Link的Elements屬性,也就是一個DefaultHandle類型的數組,通過數組下标的方式将第writeIndex個節點指派為要回收的Handle,然後将Handle的Stack屬性設定為null,表示目前Handle不是通過Stack進行回收的,最後将Tail節點的元素個數進行+1,表示下一次将從writeIndex+1的位置往裡寫。

1.2.5 擷取不同線程間釋放的對象

上節分析了異線程回收對象,原理是通過與Stack關聯的WeakOrderQueue進行回收。如果對象經過異線程回收之後,目前線程需要取出對象進行二次利用,目前Stack為空,則會通過目前Stack關聯的WeakOrderQueue進行取出,這也是本節要分析的擷取異線程釋放對象的内容。

在介紹之前,先看Stack類中的兩個屬性。

高并發程式設計之Netty高性能調優工具類解析

這裡的cursor、prev和head都是指向連結清單中WeakOrderQueue的指針,其中head指向最近建立的與Stack關聯的WeakOrderQueue,也就是頭節點。cursor代表的是尋找目前WeakOrderQueue,prev則是cursor的上一個節點,如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

我們從擷取對象的入口方法Handle的get()方法開始分析。

高并發程式設計之Netty高性能調優工具類解析

這塊邏輯我們并不陌生,Stack對象通過pop()方法彈出一個Handle,跟進pop()方法。

高并發程式設計之Netty高性能調優工具類解析
高并發程式設計之Netty高性能調優工具類解析

這裡重點關注的是,如果size為空,也就是目前Stack為空的情況下,會執行到scavenge()方法。這個方法就是從WeakOrderQueue擷取對象的方法。跟進scavenge()方法。

高并發程式設計之Netty高性能調優工具類解析

scavengeSome()方法表示如果已經回收到了對象,則直接傳回;如果沒有回收到對象,則将prev和cursor兩個指針進行重置。繼續跟進scavengeSome()方法。

高并發程式設計之Netty高性能調優工具類解析
高并發程式設計之Netty高性能調優工具類解析

首先擷取cursor指針,cursor指針代表要回收的WeakOrderQueue。如果cursor為空,則讓其指向頭節點,如果頭節點也為空,說明目前Stack沒有與其關聯的WeakOrderQueue,則傳回false。通過一個布爾值success标記回收狀态,然後擷取prev指針,也就是cursor的上一個節點,之後進入一個do-while循環。do-while循環的終止條件是沒有周遊到最後一個節點并且回收的狀态為false。我們仔細來看do-while循環中的代碼邏輯,首先cursor指針會調用transfer()方法,該方法表示從目前指針指向的WeakOrderQueue中将元素放入目前Stack中,如果取出成功則将success設定為true并跳出循環,transfer()方法我們稍後分析。

繼續往下看,如果沒有獲得元素,則會通過next屬性擷取下一個WeakOrderQueue,然後進入一個判斷if(cursor.owner.get()==null)。owner屬性是與目前WeakOrderQueue關聯的一個線程,get()方法獲得關聯的線程對象,如果這個對象為null說明該線程不存在,則進入if塊,也就是一些清理的工作。if塊中又進入一個判斷if(cursor.hasFinalData()),表示目前的WeakOrderQueue中是否還有資料,如果有資料則通過for循環将資料通過transfer()方法傳輸到目前Stack中,傳輸成功的,将success标記為true。Transfer()方法是将WeakOrderQueue中一個Link中的Handle往Stack傳輸,這裡通過for循環将每個Link中的資料都傳輸到Stack中。

繼續往下看,如果prev節點不為空,則通過prev.next=next将cursor節點進行釋放,也就是prev的下一個節點指向cursor的下一個節點。繼續往下看else塊中的prev=cursor,表示如果目前線程還在,則将prev指派為cursor,代表prev後移一個節點,最後通過cursor=next将cursor後移一位,然後繼續進行循環。循環結束之後,将Stack的prev和cursor屬性進行儲存。我們跟到transfer()方法中,分析如何将WeakOrderQueue中的Handle傳輸到Stack中。

高并發程式設計之Netty高性能調優工具類解析
高并發程式設計之Netty高性能調優工具類解析

剖析之前,我們回顧WeakOrderQueue的資料結構,如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

我們上節分析過,WeakOrderQueue是由多個Link組成的,每個Link通過連結清單的方式進行關聯,其中Head屬性指向第一個Link,Tail屬性指向最後一個Link。在每個Link中有多個Handle,Link中維護了一個讀指針readIndex,辨別着讀取Link中Handle的位置。繼續分析transfer()方法,首先擷取頭節點,并判斷頭節點是否為空,如果頭節點為空,說明目前WeakOrderQueue并沒有Link,傳回false。if(head.readIndex==LINK_CAPACITY)判斷讀指針是否為16,因為Link中元素最大數量就是16,是以如果讀指針為16,說明目前Link中的資料都被取走了。接着判斷head.next==null,表示是否還有下一個Link,如果沒有,則說明目前WeakOrderQueue沒有元素了,傳回false。如果目前Head的下一個節點不為null,則将目前頭節點指向下一個節點,将原來的頭節點進行釋放,其移動關系如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

繼續往下看,擷取頭節點的讀指針和Head中元素的數量,計算可以傳輸元素的大小,如果大小為0,則傳回false,如下圖所示。

高并發程式設計之Netty高性能調優工具類解析

接着,擷取目前Stack的大小,目前Stack大小加上可以傳輸的大小表示Stack中所需要的容量。if(expectedCapacity>dst.elements.length)表示如果需要的容量大于目前Stack中所維護的數組的大小,則将Stack中維護的數組進行擴容,進入if塊中。擴容之後會傳回actualCapacity,表示擴容之後的大小。再看srcEnd=min(srcStart+actualCapacity-dstSize,srcEnd)這步,srcEnd表示可以從Link中擷取的最後一個元素的下标。

這裡對srcStart+actualCapacity-dstSize進行拆分,actualCapacity-dstSize表示擴容後的大小-原Stack的大小,也就是最多能往Stack中傳輸多少元素。讀指針+可以往Stack傳輸的數量和,表示往Stack中傳輸的最後一個下标,這裡的下标和srcEnd中取一個較小的值,也就是既不能超過Stack的容量,也不能造成目前Link中下标越界。

繼續往下看,int newDstSize=dstSize表示初始化Stack的下标,表示Stack中從這個下标開始添加資料。然後判斷srcStart!=srcEnd,表示能不能從Link中擷取内容,如果不能,則傳回false,如果可以,則進入if塊中,接着擷取目前Link的數組Elements和Stack中的數組Elements,通過for循環,以數組下标的方式不斷将目前Link中的資料放入Stack中,for循環中首先擷取Link的第i個元素,接下來關注一個細節。

高并發程式設計之Netty高性能調優工具類解析

這裡element.recycleId==0表示對象沒有被回收過,則指派為lastRecycledId,lastRecycledId是WeakOrderQueue中的唯一下标,通過指派标記Element被回收過,然後繼續判斷element.recycleId!=element.lastRecycledId,表示該對象被回收過,但是回收的recycleId卻不是最後一次回收lastRecycledId,這是一種異常情況,表示一個對象在不同的地方被回收過兩次,這種情況則抛出異常,接着将Link的第i個元素設定為null,繼續往下看。

高并發程式設計之Netty高性能調優工具類解析

這裡表示控制資源回收筒回收的頻率,之前的章節中分析過,這裡不再贅述。

● element.stack=dst表示将Handle的Stack屬性設定到目前Stack。

● dstElems[newDstSize++]=element表示通過數組下标的方式将Link中的Handle指派到Stack的數組中。

繼續往下看:

高并發程式設計之Netty高性能調優工具類解析

這裡的if表示循環結束後,如果Link中的資料已經回收完畢,并且還有下一個節點則會進到reclaimSpace()方法。跟到reclaimSpace()方法。

高并發程式設計之Netty高性能調優工具類解析

将availableSharedCapacity加上16,表示WeakOrderQueue還可以繼續插入Link。繼續看transfer()方法。

● this.head=head.next表示将頭節點後移一個元素。

● head.readIndex=srcEnd表示将讀指針指向srcEnd,下一次讀取可以從srcEnd開始。

● if(dst.size==newDstSize)表示沒有向Stack傳輸任何對象,則傳回false,否則就通過dst.size=newDstSize更新Stack的大小為newDstSize,并傳回true。

以上就是從Link中往Stack中傳輸資料的過程。

繼續閱讀