1. 程式人生 > >7. SOFAJRaft原始碼分析—如何實現一個輕量級的物件池?

7. SOFAJRaft原始碼分析—如何實現一個輕量級的物件池?

前言

我在看SOFAJRaft的原始碼的時候看到了使用了物件池的技術,看了一下感覺要吃透的話還是要新開一篇文章來講,內容也比較充實,大家也可以學到之後運用到實際的專案中去。

這裡我使用RecyclableByteBufferList來作為講解的例子:

RecyclableByteBufferList

public final class RecyclableByteBufferList extends ArrayList<ByteBuffer> implements Recyclable {

    private transient final Recyclers.Handle handle;

    private static final Recyclers<RecyclableByteBufferList> recyclers = new Recyclers<RecyclableByteBufferList>(512) {

        @Override
        protected RecyclableByteBufferList newObject(final Handle handle) {
            return new RecyclableByteBufferList(
                    handle);
        }
    };

      //獲取一個RecyclableByteBufferList例項
    public static RecyclableByteBufferList newInstance(final int minCapacity) {
        final RecyclableByteBufferList ret = recyclers.get();
        //容量不夠的話,進行擴容
        ret.ensureCapacity(minCapacity);
        return ret;
    }
      //回收RecyclableByteBufferList物件
    @Override
    public boolean recycle() {
        clear();
        this.capacity = 0;
        return recyclers.recycle(this, handle);
    }
}

我在上面將RecyclableByteBufferList獲取物件的方法和回收物件的方法給列舉出來了,獲取例項的時候會通過recyclers的get方法去獲取,回收物件的時候會去呼叫list的clear方法清空list裡面的內容之後再去呼叫recyclers的recycle方法進行回收。
如果recyclers裡面沒有物件可以獲取,那麼會呼叫newObject方法建立一個物件,然後將handle物件傳入構造器中進行例項化。

物件池Recyclers

資料結構

  1. 每一個 Recyclers 物件包含一個 ThreadLocal<Stack<T>> threadLocal
    例項;
    每一個執行緒包含一個 Stack 物件,該 Stack 物件包含一個 DefaultHandle[],而 DefaultHandle 中有一個屬性 T value,用於儲存真實物件。也就是說,每一個被回收的物件都會被包裝成一個 DefaultHandle 物件
  2. 每一個 Recyclers 物件包含一個ThreadLocal<Map<Stack<?>, WeakOrderQueue>> delayedRecycled例項;
    每一個執行緒物件包含一個 Map<Stack<?>, WeakOrderQueue>,儲存著為其他執行緒建立的 WeakOrderQueue 物件,WeakOrderQueue 物件中儲存一個以 Head 為首的 Link 陣列,每個 Link 物件中儲存一個 DefaultHandle[] 陣列,用於存放回收物件。

假設執行緒A建立的物件

  1. 執行緒A回收RecyclableByteBufferList時,直接將RecyclableByteBufferList的DefaultHandle 物件壓入 Stack 的 DefaultHandle[] 中;
  2. 執行緒B回收RecyclableByteBufferList時,會首先從其 Map<Stack<?>, WeakOrderQueue> 物件中獲取 key=執行緒A的Stack 物件的 WeakOrderQueue,然後直接將RecyclableByteBufferList的DefaultHandle 物件(內部包含RecyclableByteBufferList物件)壓入該 WeakOrderQueue 中的 Link 連結串列中的尾部 Link 的 DefaultHandle[]中,同時,這個 WeakOrderQueue 會與執行緒 A 的 Stack 中的 head 屬性進行關聯,用於後續物件的 pop 操作;
  3. 當執行緒 A 從物件池獲取物件時,如果執行緒 A 的 Stack 中有物件,則直接彈出;如果沒有物件,則先從其 head 屬性所指向的 WeakorderQueue 開始遍歷 queue 連結串列,將 RecyclableByteBufferList 物件從其他執行緒的 WeakOrderQueue 中轉移到執行緒 A 的 Stack 中(一次 pop 操作只轉移一個包含了元素的 Link),再彈出。

Recyclers靜態程式碼塊

private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024; // Use 4k instances as default.
private static final int DEFAULT_MAX_CAPACITY_PER_THREAD;
private static final int INITIAL_CAPACITY;

static {
    // 每個執行緒的最大物件池容量
    int maxCapacityPerThread = SystemPropertyUtil.getInt("jraft.recyclers.maxCapacityPerThread", DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD);
    if (maxCapacityPerThread < 0) {
        maxCapacityPerThread = DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD;
    }

    DEFAULT_MAX_CAPACITY_PER_THREAD = maxCapacityPerThread;
    if (LOG.isDebugEnabled()) {
        if (DEFAULT_MAX_CAPACITY_PER_THREAD == 0) {
            LOG.debug("-Djraft.recyclers.maxCapacityPerThread: disabled");
        } else {
            LOG.debug("-Djraft.recyclers.maxCapacityPerThread: {}", DEFAULT_MAX_CAPACITY_PER_THREAD);
        }
    }
    // 設定初始化容量資訊
    INITIAL_CAPACITY = Math.min(DEFAULT_MAX_CAPACITY_PER_THREAD, 256);
}

 public static final Handle NOOP_HANDLE = new Handle() {};

Recyclers會在靜態程式碼塊中做一些物件池容量初始化的工作,初始化了最大物件池容量和初始化容量資訊。

從物件池中獲取物件

Recyclers#get

// 執行緒變數,儲存每個執行緒的物件池資訊,通過 ThreadLocal 的使用,避免了不同執行緒之間的競爭情況
private final ThreadLocal<Stack<T>> threadLocal = new ThreadLocal<Stack<T>>() {

    @Override
    protected Stack<T> initialValue() {
        return new Stack<>(Recyclers.this, Thread.currentThread(), maxCapacityPerThread);
    }
};

public final T get() {
    if (maxCapacityPerThread == 0) {
        return newObject(NOOP_HANDLE);
    }
    //從threadLocal中獲取一個棧物件
    Stack<T> stack = threadLocal.get();
    //拿出棧頂元素
    DefaultHandle handle = stack.pop();
    //如果棧裡面沒有元素,那麼就例項化一個
    if (handle == null) {
        handle = stack.newHandle();
        handle.value = newObject(handle);
    }
    return (T) handle.value;
}

Get方法會從threadLocal中去獲取資料,如果獲取不到,那麼會初始化一個Stack,並傳入當前Recyclers例項,當前執行緒,與最大容量。然後從stack中pop拿出棧頂元素,如果獲取的元素為空,那麼直接呼叫newHandle新建一個DefaultHandle例項,並呼叫Recyclers實現類的newObject獲取實現類的例項。也就是說DefaultHandle是用來封裝真正的物件的例項。

從stack中申請一個物件

Stack(Recyclers<T> parent, Thread thread, int maxCapacity) {
    this.parent = parent;
    this.thread = thread;
    this.maxCapacity = maxCapacity;
    elements = new DefaultHandle[Math.min(INITIAL_CAPACITY, maxCapacity)];
}

DefaultHandle pop() {
    int size = this.size;
    if (size == 0) {
        if (!scavenge()) {
            return null;
        }
        size = this.size;
    }
    //size表示整個stack中的大小
    size--;
    //獲取最後一個元素
    DefaultHandle ret = elements[size];
    if (ret.lastRecycledId != ret.recycleId) {
        throw new IllegalStateException("recycled multiple times");
    }
    // 清空回收資訊,以便判斷是否重複回收
    ret.recycleId = 0;
    ret.lastRecycledId = 0;
    this.size = size;
    return ret;
}

獲取物件的邏輯也比較簡單,當 Stack 中的 DefaultHandle[] 的 size 為 0 時,需要從其他執行緒的 WeakOrderQueue 中轉移資料到 Stack 中的 DefaultHandle[],即 scavenge方法,該方法下面再聊。當 Stack 中的 DefaultHandle[] 中最終有了資料時,直接獲取最後一個元素

物件池回收物件

我們再來看看RecyclableByteBufferList是怎麼回收物件的。
RecyclableByteBufferList#recycle

public boolean recycle() {
    clear();
    this.capacity = 0;
    return recyclers.recycle(this, handle);
}

RecyclableByteBufferList回收物件的時候首先會呼叫clear方法清空屬性,然後呼叫recyclers的recycle方法進行物件回收。

Recyclers#recycle

public final boolean recycle(T o, Handle handle) {
    if (handle == NOOP_HANDLE) {
        return false;
    }

    DefaultHandle h = (DefaultHandle) handle;
    //stack在例項化的時候會在構造器中傳入一個Recyclers作為parent
    //所以這裡是校驗一下,如果不是當前執行緒的, 直接不回收了
    if (h.stack.parent != this) {
        return false;
    }
    if (o != h.value) {
        throw new IllegalArgumentException("o does not belong to handle");
    }
    h.recycle();
    return true;
}

這裡會接著呼叫DefaultHandle的recycle方法進行回收

DefaultHandle

static final class DefaultHandle implements Handle {
    //在WeakOrderQueue的add方法中會設定成ID
    //在push方法中設定成為OWN_THREAD_ID
    //在pop方法中設定為0
    private int lastRecycledId;
    //只有在push方法中才會設定OWN_THREAD_ID
    //在pop方法中設定為0
    private int recycleId;
    //當前的DefaultHandle物件所屬的Stack
    private Stack<?> stack;
    private Object value;

    DefaultHandle(Stack<?> stack) {
        this.stack = stack;
    }

    public void recycle() {
        Thread thread = Thread.currentThread();
        //如果當前執行緒正好等於stack所對應的執行緒,那麼直接push進去
        if (thread == stack.thread) {
            stack.push(this);
            return;
        }
        // we don't want to have a ref to the queue as the value in our weak map
        // so we null it out; to ensure there are no races with restoring it later
        // we impose a memory ordering here (no-op on x86)
        // 如果不是當前執行緒,則需要延遲迴收,獲取當前執行緒儲存的延遲迴收WeakHashMap
        Map<Stack<?>, WeakOrderQueue> delayedRecycled = Recyclers.delayedRecycled.get();
        // 當前 handler 所在的 stack 是否已經在延遲迴收的任務佇列中
        // 並且 WeakOrderQueue是一個多執行緒間可以共享的Queue
        WeakOrderQueue queue = delayedRecycled.get(stack);
        if (queue == null) {
            delayedRecycled.put(stack, queue = new WeakOrderQueue(stack, thread));
        }
        queue.add(this);
    }
}

DefaultHandle在例項化的時候會傳入一個stack例項,代表當前例項是屬於這個stack的。
所以在呼叫recycle方法的時候,會判斷一下,當前的執行緒是不是stack所屬的執行緒,如果是那麼直接push到stack裡面就好了,不是則呼叫延遲佇列delayedRecycled;
從delayedRecycled佇列中獲取Map<Stack<?>, WeakOrderQueue> delayedRecycled ,根據stack作為key來獲取WeakOrderQueue,然後將當前的DefaultHandle例項放入到WeakOrderQueue中。

同線程回收物件

Stack#push

void push(DefaultHandle item) {
    // (item.recycleId | item.lastRecycleId) != 0 等價於 item.recycleId!=0 && item.lastRecycleId!=0
    // 當item開始建立時item.recycleId==0 && item.lastRecycleId==0
    // 當item被recycle時,item.recycleId==x,item.lastRecycleId==y 進行賦值
    // 當item被pop之後, item.recycleId = item.lastRecycleId = 0
    // 所以當item.recycleId 和 item.lastRecycleId 任何一個不為0,則表示回收過
    if ((item.recycleId | item.lastRecycledId) != 0) {
        throw new IllegalStateException("recycled already");
    }
    // 設定物件的回收id為執行緒id資訊,標記自己的被回收的執行緒資訊
    item.recycleId = item.lastRecycledId = OWN_THREAD_ID;

    int size = this.size;
    if (size >= maxCapacity) {
        // Hit the maximum capacity - drop the possibly youngest object.
        return;
    }
    // stack中的elements擴容兩倍,複製元素,將新陣列賦值給stack.elements
    if (size == elements.length) {
        elements = Arrays.copyOf(elements, Math.min(size << 1, maxCapacity));
    }

    elements[size] = item;
    this.size = size + 1;
}

同線程回收物件 DefaultHandle#recycle 步驟:

  1. stack 先檢測當前的執行緒是否是建立 stack 的執行緒,如果不是,則走異執行緒回收邏輯;如果是,則首先判斷是否重複回收,然後判斷 stack 的 DefaultHandle[] 中的元素個數是否已經超過最大容量(4k),如果是,直接返回;
  2. 判斷當前的 DefaultHandle[] 是否還有空位,如果沒有,以 maxCapacity 為最大邊界擴容 2 倍,之後拷貝舊陣列的元素到新陣列,然後將當前的 DefaultHandle 物件放置到 DefaultHandle[] 中
  3. 最後重置 stack.size 屬性

異執行緒回收物件

WeakOrderQueue

static final class Stack<T> {
    //使用volatile可以立即讀取到該queue
      private volatile WeakOrderQueue head;
}
WeakOrderQueue(Stack<?> stack, Thread thread) {
    head = tail = new Link();
    //使用的是WeakReference ,作用是在poll的時候,如果owner不存在了
    // 則需要將該執行緒所包含的WeakOrderQueue的元素釋放,然後從連結串列中刪除該Queue。
    owner = new WeakReference<>(thread);
    //假設執行緒B和執行緒C同時回收執行緒A的物件時,有可能會同時建立一個WeakOrderQueue,就坑同時設定head,所以這裡需要加鎖
    synchronized (stackLock(stack)) {
        next = stack.head;
        stack.head = this;
    }
}

建立WeakOrderQueue物件的時候會初始化一個WeakReference的owner,作用是在poll的時候,如果owner不存在了, 則需要將該執行緒所包含的WeakOrderQueue的元素釋放,然後從連結串列中刪除該Queue。

然後給stack加鎖,假設執行緒B和執行緒C同時回收執行緒A的物件時,有可能會同時建立一個WeakOrderQueue,就坑同時設定head,所以這裡需要加鎖。

以head==null的時候為例
加鎖:
執行緒B先執行,則head = 執行緒B的queue;之後執行緒C執行,此時將當前的head也就是執行緒B的queue作為執行緒C的queue的next,組成連結串列,之後設定head為執行緒C的queue
不加鎖:
執行緒B先執行 next = stack.head此時執行緒B的queue.next=null->執行緒C執行next = stack.head;執行緒C的queue.next=null-> 執行緒B執行stack.head = this;設定head為執行緒B的queue -> 執行緒C執行stack.head = this;設定head為執行緒C的queue,此時執行緒B和執行緒C的queue沒有連起來。

WeakOrderQueue#add

void add(DefaultHandle handle) {
    // 設定handler的最近一次回收的id資訊,標記此時暫存的handler是被誰回收的
    handle.lastRecycledId = id;

    Link tail = this.tail;
    int writeIndex;
    // 判斷一個Link物件是否已經滿了:
    // 如果沒滿,直接新增;
    // 如果已經滿了,建立一個新的Link物件,之後重組Link連結串列,然後新增元素的末尾的Link(除了這個Link,前邊的Link全部已經滿了)
    if ((writeIndex = tail.get()) == LINK_CAPACITY) {
        this.tail = tail = tail.next = new Link();
        writeIndex = tail.get();
    }
    tail.elements[writeIndex] = handle;
    // 如果使用者在將DefaultHandle物件壓入佇列後,將Stack設定為null
    // 但是此處的DefaultHandle是持有stack的強引用的,則Stack物件無法回收;
    //而且由於此處DefaultHandle是持有stack的強引用,WeakHashMap中對應stack的WeakOrderQueue也無法被回收掉了,導致記憶體洩漏
    handle.stack = null;
    // we lazy set to ensure that setting stack to null appears before we unnull it in the owning thread;
    // this also means we guarantee visibility of an element in the queue if we see the index updated
    // tail本身繼承於AtomicInteger,所以此處直接對tail進行+1操作
    tail.lazySet(writeIndex + 1);
}

Stack異執行緒push物件流程:

  1. 首先獲取當前執行緒的 Map<Stack<?>, WeakOrderQueue> 物件,如果沒有就建立一個空 map;
  2. 然後從 map 物件中獲取 key 為當前的 Stack 物件的 WeakOrderQueue;
  3. 如果獲取的WeakOrderQueue物件為null,那麼建立一個WeakOrderQueue物件,並將物件放入到map中,最後呼叫WeakOrderQueue#add新增物件

WeakOrderQueue 的建立流程:

  1. 建立一個Link物件,將head和tail的引用都設定為此物件
  2. 建立一個WeakReference指向owner物件,設定當前的 WeakOrderQueue 所屬的執行緒為當前執行緒。
  3. 先將原本的 stack.head 賦值給剛剛建立的 WeakOrderQueue 的 next 節點,之後將剛剛建立的 WeakOrderQueue 設定為 stack.head(這一步非常重要:假設執行緒 A 建立物件,此處是執行緒 C 回收物件,則執行緒 C 先獲取其 Map<Stack<?>, WeakOrderQueue> 物件中 key=執行緒A的stack物件的 WeakOrderQueue,然後將該 Queue 賦值給執行緒 A 的 stack.head,後續的 pop 操作打基礎),形成 WeakOrderQueue 的連結串列結構。

WeakOrderQueue#add新增物件流程

  1. 首先設定 item.lastRecycledId = 當前 WeakOrderQueue 的 id
  2. 然後看當前的 WeakOrderQueue 中的 Link 節點連結串列中的尾部 Link 節點的 DefaultHandle[] 中的元素個數是否已經達到 LINK_CAPACITY(16)
  3. 如果不是,則直接將當前的 DefaultHandle 元素插入尾部 Link 節點的 DefaultHandle[] 中,之後置空當前的 DefaultHandle 元素的 stack 屬性,最後記錄當前的 DefaultHandle[] 中的元素數量;
  4. 如果是,則新建一個 Link,並且放在當前的 Link 連結串列中的尾部節點處,與之前的 tail 節點連起來(連結串列),之後進行第三步的操作。

從異執行緒獲取物件

我再把pop方法搬下來一次:

DefaultHandle pop() {
    int size = this.size;
    // size=0 則說明本執行緒的Stack沒有可用的物件,先從其它執行緒中獲取。
    if (size == 0) {
        // 當 Stack<T> 此時的容量為 0 時,去 WeakOrder 中轉移部分物件到 Stack 中
        if (!scavenge()) {
            return null;
        }
        //由於在transfer(Stack<?> dst)的過程中,可能會將其他執行緒的WeakOrderQueue中的DefaultHandle物件傳遞到當前的Stack,
        //所以size發生了變化,需要重新賦值
        size = this.size;
    }
    //size表示整個stack中的大小
    size--;
    //獲取最後一個元素
    DefaultHandle ret = elements[size];
    if (ret.lastRecycledId != ret.recycleId) {
        throw new IllegalStateException("recycled multiple times");
    }
    // 清空回收資訊,以便判斷是否重複回收
    ret.recycleId = 0;
    ret.lastRecycledId = 0;
    this.size = size;
    return ret;
}
  1. 首先獲取當前的 Stack 中的 DefaultHandle 物件中的元素個數。
  2. 如果為 0,則從其他執行緒的與當前的 Stack 物件關聯的 WeakOrderQueue 中獲取元素,並轉移到 Stack 的 DefaultHandle[] 中(每一次 pop 只轉移一個有元素的 Link),如果轉移不成功,說明沒有元素可用,直接返回 null;
  3. 如果轉移成功,則重置 size屬性 = 轉移後的 Stack 的 DefaultHandle[] 的 size,之後直接獲取 Stack 物件中 DefaultHandle[] 的最後一位元素,之後做防護性檢測,最後重置當前的 stack 物件的 size 屬性以及獲取到的 DefaultHandle 物件的 recycledId 和 lastRecycledId 回收標記,返回 DefaultHandle 物件。

scavenge轉移

Stack#scavenge

boolean scavenge() {
    // continue an existing scavenge, if any
    // 掃描判斷是否存在可轉移的 Handler
    if (scavengeSome()) {
        return true;
    }
    
    // reset our scavenge cursor
    prev = null;
    cursor = head;
    return false;
}

呼叫scavengeSome掃描判斷是否存在可轉移的 Handler,如果沒有,那麼就返回false,表示沒有可用物件

Stack#scavengeSome

boolean scavengeSome() {
    WeakOrderQueue cursor = this.cursor;
    if (cursor == null) {
        cursor = head;
        // 如果head==null,表示當前的Stack物件沒有WeakOrderQueue,直接返回
        if (cursor == null) {
            return false;
        }
    }

    boolean success = false;
    WeakOrderQueue prev = this.prev;
    do {
        // 從當前的WeakOrderQueue節點進行 handler 的轉移
        if (cursor.transfer(this)) {
            success = true;
            break;
        }
        // 遍歷下一個WeakOrderQueue
        WeakOrderQueue next = cursor.next;
        // 如果 WeakOrderQueue 的實際持有執行緒因GC回收了
        if (cursor.owner.get() == null) {
            // If the thread associated with the queue is gone, unlink it, after
            // performing a volatile read to confirm there is no data left to collect.
            // We never unlink the first queue, as we don't want to synchronize on updating the head.
            // 如果當前的WeakOrderQueue的執行緒已經不可達了
            //如果該WeakOrderQueue中有資料,則將其中的資料全部轉移到當前Stack中
            if (cursor.hasFinalData()) {
                for (;;) {
                    if (cursor.transfer(this)) {
                        success = true;
                    } else {
                        break;
                    }
                }
            }
            //將當前的WeakOrderQueue的前一個節點prev指向當前的WeakOrderQueue的下一個節點,
            // 即將當前的WeakOrderQueue從Queue連結串列中移除。方便後續GC
            if (prev != null) {
                prev.next = next;
            }
        } else {
            prev = cursor;
        }

        cursor = next;

    } while (cursor != null && !success);

    this.prev = prev;
    this.cursor = cursor;
    return success;
}
  1. 首先設定當前操作的 WeakOrderQueue cursor,如果為 null,則賦值為 stack.head 節點,如果 stack.head 為 null,則表明外部執行緒沒有回收過當前執行緒建立的 物件,外部執行緒在回收物件的時候會建立一個WeakOrderQueue,並將stack.head 指向新建立的WeakOrderQueue物件,則直接返回 false;如果不為 null,則繼續向下執行;
  2. 首先對當前的 cursor 進行元素的轉移,如果轉移成功,則跳出迴圈,設定 prev 和 cursor 屬性;
  3. 如果轉移不成功,獲取下一個執行緒 Y 中的與當前執行緒的 Stack 物件關聯的 WeakOrderQueue,如果該 queue 所屬的執行緒 Y 還可達,則直接設定 cursor 為該 queue,進行下一輪迴圈;如果該 queue 所屬的執行緒 Y 不可達了,則判斷其內是否還有元素,如果有,全部轉移到當前執行緒的 Stack 中,之後將執行緒 Y 的 queue 從查詢 queue 連結串列中移除。

transfer轉移

    boolean transfer(Stack<?> dst) {
        //尋找第一個Link
        Link head = this.head;
        // head == null,沒有儲存資料的節點,直接返回
        if (head == null) {
            return false;
        }
        // 讀指標的位置已經到達了每個 Node 的儲存容量,如果還有下一個節點,進行節點轉移
        if (head.readIndex == LINK_CAPACITY) {
            //判斷當前的Link節點的下一個節點是否為null,如果為null,說明已經達到了Link連結串列尾部,直接返回,
            if (head.next == null) {
                return false;
            }
            // 否則,將當前的Link節點的下一個Link節點賦值給head和this.head.link,進而對下一個Link節點進行操作
            this.head = head = head.next;
        }
        // 獲取Link節點的readIndex,即當前的Link節點的第一個有效元素的位置
        final int srcStart = head.readIndex;
        // 獲取Link節點的writeIndex,即當前的Link節點的最後一個有效元素的位置
        int srcEnd = head.get();
        // 本次可轉移的物件數量(寫指標減去讀指標)
        final int srcSize = srcEnd - srcStart;
        if (srcSize == 0) {
            return false;
        }
        // 獲取轉移元素的目的地Stack中當前的元素個數
        final int dstSize = dst.size;
        // 計算期盼的容量
        final int expectedCapacity = dstSize + srcSize;
        // 期望的容量大小與實際 Stack 所能承載的容量大小進行比對,取最小值
        if (expectedCapacity > dst.elements.length) {
            final int actualCapacity = dst.increaseCapacity(expectedCapacity);
            srcEnd = Math.min(srcStart + actualCapacity - dstSize, srcEnd);
        }

        if (srcStart != srcEnd) {
            // 獲取Link節點的DefaultHandle[]
            final DefaultHandle[] srcElems = head.elements;
            // 獲取目的地Stack的DefaultHandle[]
            final DefaultHandle[] dstElems = dst.elements;
            // dst陣列的大小,會隨著元素的遷入而增加,如果最後發現沒有增加,那麼表示沒有遷移成功任何一個元素
            int newDstSize = dstSize;
            //// 進行物件轉移
            for (int i = srcStart; i < srcEnd; i++) {
                DefaultHandle element = srcElems[i];
                // 表明自己還沒有被任何一個 Stack 所回收
                if (element.recycleId == 0) {
                    element.recycleId = element.lastRecycledId;
                //  避免物件重複回收
                } else if (element.recycleId != element.lastRecycledId) {
                    throw new IllegalStateException("recycled already");
                }
                // 將可轉移成功的DefaultHandle元素的stack屬性設定為目的地Stack
                element.stack = dst;
                // 將DefaultHandle元素轉移到目的地Stack的DefaultHandle[newDstSize ++]中
                dstElems[newDstSize++] = element;
                // 設定為null,清楚暫存的handler資訊,同時幫助 GC
                srcElems[i] = null;
            }
            // 將新的newDstSize賦值給目的地Stack的size
            dst.size = newDstSize;

            if (srcEnd == LINK_CAPACITY && head.next != null) {
                // 將Head指向下一個Link,也就是將當前的Link給回收掉了
                // 假設之前為Head -> Link1 -> Link2,回收之後為Head -> Link2
                this.head = head.next;
            }
            // 設定讀指標位置
            head.readIndex = srcEnd;
            return true;
        } else {
            // The destination stack is full already.
            return false;
        }
    }
}
  1. 尋找 cursor 節點中的第一個 Link如果為 null,則表示沒有資料,直接返回;
  2. 如果第一個 Link 節點的 readIndex 索引已經到達該 Link 物件的 DefaultHandle[] 的尾部,則判斷當前的 Link 節點的下一個節點是否為 null,如果為 null,說明已經達到了 Link 連結串列尾部,直接返回,否則,將當前的 Link 節點的下一個 Link 節點賦值給 head ,進而對下一個 Link 節點進行操作;
  3. 獲取 Link 節點的 readIndex,即當前的 Link 節點的第一個有效元素的位置
  4. 獲取 Link 節點的 writeIndex,即當前的 Link 節點的最後一個有效元素的位置
  5. 計算 Link 節點中可以被轉移的元素個數,如果為 0,表示沒有可轉移的元素,直接返回
  6. 獲取轉移元素的目標 Stack 中當前的元素個數(dstSize)並計算期盼的容量 expectedCapacity,如果 expectedCapacity 大於目標Stack 的長度(dst.elements.length),則先對目的地 Stack 進行擴容,計算 Link 中最終的可轉移的最後一個元素的下標;
  7. 如果發現目的地 Stack 已經滿了( srcStart != srcEnd為false),則直接返回 false
  8. 獲取 Link 節點的 DefaultHandle[] (srcElems)和目標 Stack 的 DefaultHandle[](dstElems)
  9. 根據可轉移的起始位置和結束位置對 Link 節點的 DefaultHandle[] 進行迴圈操作
  10. 將可轉移成功的 DefaultHandle 元素的stack屬性設定為目標 Stack(element.stack = dst),將 DefaultHandle 元素轉移到目的地 Stack 的 DefaultHandle[newDstSize++] 中,最後置空 Link 節點的 DefaultHandle[i]
  11. 如果當前被遍歷的 Link 節點的 DefaultHandle[] 已經被掏空了(srcEnd == LINK_CAPACITY),並且該 Link 節點還有下一個 Link 節點
  12. 重置當前 Link 的 readIndex