preface
This chapter describes Netty object pools.
- How to use Netty object pool to create and recycle objects
- How does Netty abstract object pools
- How does Netty implement object pooling
- How to handle the scenario where the same object is created and recycled on different threads
How to use Netty object pool
Item is a common object that is managed by the object pool.
public static class Item {
// Class variable, an Item object pool
private static final ObjectPool<Item> pool = ObjectPool.newPool(new ObjectPool.ObjectCreator<Item>() {
@Override
public Item newObject(ObjectPool.Handle<Item> handle) {
return newItem(handle); }});// Common business attributes
private int id;
// The object retrieves the hook
private final ObjectPool.Handle<Item> handle;
/** * private constructor */
private Item(ObjectPool.Handle<Item> handle) {
this.handle = handle;
}
/** * gets item */ from the object pool
public static Item getInstance(a) {
return pool.get();
}
/** * reclaim */
public void recycle(a) {
this.handle.recycle(this);
}
public int getId(a) {
return id;
}
public void setId(int id) {
this.id = id; }}Copy the code
Objects are then created and reclaimed using the object pool.
@Test
public void test(a) {
Item item01 = Item.getInstance(); // There is no object cache in the pool, a new object is created
item01.setId(1);
System.out.println(item01); // 5c7fa833
item01.recycle(); // Retrieve item01 with the handle hook
item01 = Item.getInstance(); // Retrieve an object from the pool again
System.out.println(item01); // 5c7fa833
System.out.println(item01.getId()); / / 1
}
Copy the code
Object pool abstract structure
Netty designs its own abstraction structure for ObjectPool, including ObjectPool, object creation, and object reclamation. These three abstract definitions are all in ObjectPool class.
1. Object pool
ObjectPool abstract class, which is the top-level abstraction of the ObjectPool, requires subclasses to implement a get method. If there is an object in the pool, it is returned directly by the get method, otherwise a newObject is created by ObjectCreator#newObject(Handle).
/**
* Light-weight object pool.
*
* @param <T> the type of the pooled object
*/
public abstract class ObjectPool<T> {
ObjectPool() { }
/**
* Get a {@link Object} from the {@link ObjectPool}. The returned {@link Object} may be created via
* {@link ObjectCreator#newObject(Handle)} if no pooled {@link Object} is ready to be reused.
*/
public abstract T get(a);
}
Copy the code
2. Object creation
ObjectCreator is an abstract interface for object creation. You need to implement a newObject method, pass in Handle, and create a newObject, which can then be recycled by Handle#recycle. By implication, an object needs to hold a Handle that can reclaim the current instance to ObjectPool at some point in the future.
/**
* Creates a new Object which references the given {@link Handle} and calls {@link Handle#recycle(Object)} once
* it can be re-used.
*
* @param <T> the type of the pooled object
*/
public interface ObjectCreator<T> {
/**
* Creates an returns a new {@link Object} that can be used and later recycled via
* {@link Handle#recycle(Object)}.
*/
T newObject(Handle<T> handle);
}
Copy the code
3. Object reclamation
Handle is passed in ObjectCreator when the object is created. The client needs to recycle the object by using the Recycle method of Handle.
/**
* Handle for an pooled {@link Object} that will be used to notify the {@link ObjectPool} once it can
* reuse the pooled {@link Object} again.
* @param <T>
*/
public interface Handle<T> {
/**
* Recycle the {@link Object} if possible and so make it ready to be reused.
*/
void recycle(T self);
}
Copy the code
Netty implements object pool
Netty implements its own lightweight ObjectPool for the above abstract interface. The realization of this lightweight ObjectPool is the static internal class RecyclerObjectPool of ObjectPool.
private static final class RecyclerObjectPool<T> extends ObjectPool<T> {
private final Recycler<T> recycler;
RecyclerObjectPool(final ObjectCreator<T> creator) {
recycler = new Recycler<T>() {
@Override
protected T newObject(Handle<T> handle) {
returncreator.newObject(handle); }}; }@Override
public T get(a) {
returnrecycler.get(); }}Copy the code
As you can see, creating a RecyclerObjectPool requires passing in the implementation class of the ObjectCreator interface and no additional methods. RecyclerObjectPool actually creates a Recycler. By using ObjectCreator, Recycler can create objects when there are no cached objects in the pool.
Netty also provides a static method in ObjectPool to create a RecyclerObjectPool.
/**
* Creates a new {@link ObjectPool} which will use the given {@link ObjectCreator} to create the {@link Object}
* that should be pooled.
*/
public static <T> ObjectPool<T> newPool(final ObjectCreator<T> creator) {
return new RecyclerObjectPool<T>(ObjectUtil.checkNotNull(creator, "creator"));
}
Copy the code
Fourth, the Recycler
Recycler is a portal to the object pool to retrieve objects, so let’s start with class variables.
The first and most critical is a FastThreadLocal instance, threadLocal. Each thread manages one instance of the Stack, which is the actual pool.
private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
@Override
protected Stack<T> initialValue(a) {
return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,
interval, maxDelayedQueuesPerThread, delayedQueueInterval);
}
@Override
protected void onRemoval(Stack<T> value) {
if (value.threadRef.get() == Thread.currentThread()) {
if(DELAYED_RECYCLED.isSet()) { DELAYED_RECYCLED.get().remove(value); }}}};Copy the code
Additionally, object pooling is complicated by the fact that object creation and collection may not be done in the same thread. There is also a FastThreadLocal instance, DELAYED_RECYCLED, which holds the WeakOrderQueue container where the stack of the current thread helps the stack of other threads recycle. For example, the Stack of thread A allocates instance X, and the Stack of thread B retrieves instance X. The FastThreadLocal is for thread B, and the Map key is the Stack of thread A, and the value is the WeakOrderQueue created by thread B. The object used to store the Stack collection that helps thread A.
// Stack instances of other threads - A thread helps other threads' stack recycle objects in a WeakOrderQueue container
private static finalFastThreadLocal<Map<Stack<? >, WeakOrderQueue>> DELAYED_RECYCLED =newFastThreadLocal<Map<Stack<? >, WeakOrderQueue>>() {@Override
protectedMap<Stack<? >, WeakOrderQueue> initialValue() {return new WeakHashMap<Stack<?>, WeakOrderQueue>();
}
};
Copy the code
Recycler member variables are used to limit the size of the Recycler pool and prevent it from expanding.
private final int maxCapacityPerThread; / / 4096
private final int maxSharedCapacityFactor; / / 2
private final int interval; / / 8
private final int maxDelayedQueuesPerThread; / / auditing * 2
private final int delayedQueueInterval; / / 8
Copy the code
- MaxCapacityPerThread: Since the object pool is a thread-local cache based on the FastThreadLocal implementation, maxCapacityPerThread controls how many cached instances a Stack can hold in order to prevent too many instances from being cached.
- MaxSharedCapacityFactor: a factor used to calculate the maximum number of instances that other threads can help the current thread reclaim.
- Interval/delayedQueueInterval: in order to prevent the object pool, not every recycling object back into the object pool, behind the introduction of various kinds of container, usually once every eight will truly cache.
- MaxDelayedQueuesPerThread: each thread can help most nuclear number * 2 threads recycling.
The Recycler get method is the access method to the object pool. Use ThreadLocal to get the Stack instance corresponding to the current thread, and manipulate Stack to get Handle and value corresponding to Handle (client object instance).
public final T get(a) {
/ / the default 4096
if (maxCapacityPerThread == 0) {
return newObject((Handle<T>) NOOP_HANDLE);
}
// Get the Stack of the current thread
Stack<T> stack = threadLocal.get();
/ / for the Handle
DefaultHandle<T> handle = stack.pop();
// If there is a Handle in the stack, return the cached object directly; otherwise, create Handle
if (handle == null) {
Handle = new DefaultHandle<>(stack)
handle = stack.newHandle();
// Call the newObject method of the anonymous class in the RecycleObjectPool constructor, which is actually the anonymous ObjectCreator implemented by the client
handle.value = newObject(handle);
}
return (T) handle.value;
}
Copy the code
Fifth, DefaultHandle
DefaultHandle is a hook from the Netty object pool to the client to reclaim an object (see the example in section 1).
private static final class DefaultHandle<T> implements Handle<T> {
int lastRecycledId;
int recycleId;
booleanhasBeenRecycled; Stack<? > stack; Object value; DefaultHandle(Stack<? > stack) {this.stack = stack;
}
@Override
public void recycle(Object object) {
if(object ! = value) {throw new IllegalArgumentException("object does not belong to handle"); } Stack<? > stack =this.stack;
if(lastRecycledId ! = recycleId || stack ==null) {
throw new IllegalStateException("recycled already");
}
stack.push(this); }}Copy the code
The member variable value is an object created by the client and managed by the object pool. The Recycle method is called by the client, placing DefaultHandle on the Stack.
Sixth, the Stack
Start with the Stack legend and member variables to get an idea of its structure.
private static final class Stack<T> {
// Recycler belongs to a RecyclerObjectPool. Stack belongs to an object pool instance
final Recycler<T> parent;
// userModel -> handle -> stack -> thread
final WeakReference<Thread> threadRef;
// The maximum number of objects that can be reclaimed by other threads = Max (4096/2, 16)
final AtomicInteger availableSharedCapacity;
// Allow x threads to help recycle (the maximum number of WeakOrderQueue) = number of cores * 2
private final int maxDelayedQueues;
// Stack specifies the maximum number of defaulthandles that can be stored
// Corresponds to the maximum number of DefaultHandle that the current thread can store, since the Stack is controlled by ThreadLocal
private final int maxCapacity;
// Handle retrievals the recycle counter, which becomes 0 when interval (8)
private int handleRecycleCount;
If handleRecycleCount<8, it will be discarded
private final int interval;
// It is similar to interval, but is the threshold when a different thread is reclaimed to WeakOrderQueue. The default value is also 8
private final int delayedQueueInterval;
/ / store DefaultHandleDefaultHandle<? >[] elements;// The number of actual elements in elements
int size;
// Objects created by the current stack are reclaimed by other threads, and these instances are stored in WeakOrderQueue
private WeakOrderQueue cursor, prev; // consumption WeakOrderQueue Schedule double pointer
private volatile WeakOrderQueue head; // Stores the head node of WeakOrderQueue
}
Copy the code
Most member variables are the same as Recycler, to prevent the pool of objects from expanding from Recycler. Focus on a few member variables:
- Elements: Array of Handle instances cached by the current thread (Stack instance ThreadLocal). Initial capacity 256.
- Head: Linked list of WeakOrderQueue that stores Handle instances that other threads help the current thread reclaim.
- Cursor /prev: The current Stack gets the progress of Handle consumption from WeakOrderQueue linked list. Here is the double pointer.
- ThreadRef: Since Stack operates on ThreadLocal, it stores virtual references to the corresponding thread. The reason for using virtual references is as follows: If the current Stack corresponds to a ThreadA, the ThreadB client holds an object allocated by ThreadA, and the client holds the Handle, which holds the Stack. If the Stack strongly references ThreadA, the ThreadA cannot be recycled.
- HandleRecycleCount/interval: handleRecycleCount is a loop counter, the maximum value is equal to the interval, the initial value is equal to the interval, for each Handle recycling plus 1. If handleRecycleCount is smaller than interval, Handle is not actually put back into the pool, preventing the object pool from swelling. The default interval is 8.
1. Get objects
The POP method is used to get the cached DefaultHandle from the Stack. First fetch from the elements array collected by the current thread. If the Elements array is empty, try to move some DefaultHandle from the WeakOrderQueue into the Elements array and then fetch from the Elements array.
DefaultHandle<T> pop(a) {
int size = this.size;
// The current thread does not cache Handle, elements has no elements in the array
if (size == 0) {
// Try to get from WeakOrderQueue
if(! scavenge()) {return null;
}
size = this.size;
if (size <= 0) {
return null; }}// The current thread has a cache Handle, which is retrieved directly from the Elements array
size --;
DefaultHandle ret = elements[size];
elements[size] = null;
this.size = size;
if(ret.lastRecycledId ! = ret.recycleId) {throw new IllegalStateException("recycled multiple times");
}
ret.recycleId = 0;
ret.lastRecycledId = 0;
return ret;
}
Copy the code
If the Elements array is empty, the Scavenge method tries to iterate the WeakOrderQueue list from cursor and call the WeakOrderQueue transfer method to recycle object instances that other threads help the current thread Stack. Into the elements array of the current Stack.
private boolean scavenge(a) {
// Walk through the WeakOrderQueue linked list from cursor and try to get some DefaultHandle into the Elements array
if (scavengeSome()) {
return true;
}
// If obtaining DefaultHandle from WeakOrderQueue fails, reset cursor and prev Pointers
prev = null;
cursor = head;
return false;
}
private boolean scavengeSome(a) {
WeakOrderQueue prev;
WeakOrderQueue cursor = this.cursor;
// cursor is empty, initialize two Pointers
if (cursor == null) {
prev = null;
cursor = head;
if (cursor == null) {
return false; }}else {
prev = this.prev;
}
boolean success = false;
do {
// Try to get a Handle from another thread's Recovered WeakOrderQueue into its own elements
if (cursor.transfer(this)) {
success = true;
break;
}
WeakOrderQueue next = cursor.getNext();
// If the thread referenced by WeakOrderQueue has been reclaimed, move all handles in the WeakOrderQueue to the elements array of the current Stack
if (cursor.get() == null) {
if (cursor.hasFinalData()) {
for (;;) {
if (cursor.transfer(this)) {
success = true;
} else {
break; }}}// Remove the WeakOrderQueue of the reclaimed thread to which the cursor points from the linked list
if(prev ! =null) { cursor.reclaimAllSpaceAndUnlink(); prev.setNext(next); }}else {
prev = cursor;
}
cursor = next;
} while(cursor ! =null && !success);
this.prev = prev;
this.cursor = cursor;
return success;
}
Copy the code
2. Return the object
The entry point for returning an object is the Recycle method of DefaultHandle, which is called by the client. The PooledByteBuf abstract class, for example, uses an object pool. When the ByteBuf reference count is zero, the DealLocate method is triggered, and then the Recycle method of DefaultHandle is called. Because PooledByteBuf doesn’t actually hold the resource, just the container object that holds it (the real resource is a 16MB memory block), using an object pool can reduce the overhead and GC of new objects.
@Override
protected final void deallocate(a) {
if (handle >= 0) {
final long handle = this.handle;
this.handle = -1;
memory = null;
chunk.arena.free(chunk, tmpNioBuf, handle, maxLength, cache);
tmpNioBuf = null;
chunk = null; recycle(); }}private void recycle(a) {
// DefaultHandle#recycle(PooledByteBuf)
recyclerHandle.recycle(this);
}
Copy the code
The recycle method of DefaultHandle, which calls the Stack’s push method, puts an instance of DefaultHandle on the Stack.
void push(DefaultHandle
item) {
Thread currentThread = Thread.currentThread();
// If the current stack is created by the current thread, and the current thread is reclaiming Handle, put it directly into the Elements array
if (threadRef.get() == currentThread) {
pushNow(item);
}
// If the current stack is created by another thread, but reclaimed by the current thread, put WeakOrderQueue in WeakOrderQueue
// Insert WeakOrderQueue into the head of the WeakOrderQueue list of the stack of other threads
else{ pushLater(item, currentThread); }}Copy the code
The pushNow method puts DefaultHandle directly into the elements array of the current stack, but may discard the Handle to prevent the object pool from overexpanding.
private void pushNow(DefaultHandle
item) {
if((item.recycleId | item.lastRecycledId) ! =0) {
throw new IllegalStateException("recycled already");
}
item.recycleId = item.lastRecycledId = OWN_THREAD_ID;
int size = this.size;
// If the number of elements exceeds 4096 or dropHandle decides to discard the element to control the object pool size
if (size >= maxCapacity || dropHandle(item)) {
return;
}
if (size == elements.length) {
elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
}
elements[size] = item;
this.size = size + 1;
}
HandleRecycleCount =interval=8 * handleRecycleCount will not be discarded for the first time, 2-8 times, and the ninth time */
boolean dropHandle(DefaultHandle
handle) {
if(! handle.hasBeenRecycled) {if (handleRecycleCount < interval) {
handleRecycleCount++;
// Drop the object.
return true;
}
handleRecycleCount = 0;
handle.hasBeenRecycled = true;
}
return false;
}
Copy the code
The pushLater method places DefaultHandle in the WeakOrderQueue, or if it is a new WeakOrderQueue, inserts the WeakOrderQueue into the Head of the WeakOrderQueue list on the stack. Weakorderqueue.dummy considers an empty implementation and cannot actually hold elements. When the current thread in DELAYED_RECYCLED helps recycle more than 16 of the stack (or think of it as the number of threads), recycling is stopped.
private void pushLater(DefaultHandle
item, Thread thread) { Map<Stack<? >, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get(); WeakOrderQueue queue = delayedRecycled.get(this);
if (queue == null) {
If the current thread has already helped 16 threads to collect, it will not help other threads to collect
if (delayedRecycled.size() >= maxDelayedQueues) {
delayedRecycled.put(this, WeakOrderQueue.DUMMY);
return;
}
// If the stack's corresponding WeakOrderQueue does not exist, create and insert the stack's WeakOrderQueue header
WeakOrderQueue also has a discard policy, so it may return null
if ((queue = newWeakOrderQueue(thread)) == null) {
return;
}
delayedRecycled.put(this, queue);
} else if (queue == WeakOrderQueue.DUMMY) {
return;
}
queue.add(item);
}
private WeakOrderQueue newWeakOrderQueue(Thread thread) {
return WeakOrderQueue.newQueue(this, thread);
}
static WeakOrderQueue newQueue(Stack
stack, Thread thread) {
if(! Head.reserveSpaceForLink(stack.availableSharedCapacity)) {return null;
}
// Place the stack and current recycle thread into the WeakOrderQueue
final WeakOrderQueue queue = new WeakOrderQueue(stack, thread);
// Insert WeakOrderQueue in the stack as the head node
stack.setHead(queue);
return queue;
}
Copy the code
Seven, WeakOrderQueue
WeakOrderQueue inherits the thread that WeakReference virtualizes to perform recycling, and internally stores DefaultHandle that assists the recycling thread in the way of Link + array (Link# Elements).
private static final class WeakOrderQueue extends WeakReference<Thread> {
/ / Link list
private final Head head;
private Link tail;
// Point to the next Queue
private WeakOrderQueue next;
// An id flag
private final int id = ID_GENERATOR.getAndIncrement();
// Discard policy same Stack interval Default 8 handleRecycleCount initial 8 Value range 0-8
private final int interval;
private int handleRecycleCount;
private WeakOrderQueue(Stack
stack, Thread thread) {
super(thread);
tail = new Link();
head = new Head(stack.availableSharedCapacity);
head.link = tail;
interval = stack.delayedQueueInterval;
handleRecycleCount = interval;
}
private static final class Head {
// Max capacity default 2048 = Stack#availableSharedCapacity
private final AtomicInteger availableSharedCapacity;
// The actual header node
Link link;
}
static final class Link extends AtomicInteger {
16 / / Handle
finalDefaultHandle<? >[] elements =new DefaultHandle[LINK_CAPACITY];
// AtomicInteger serves as write index, which is the next index that can be inserted into elements
// readIndex as readIndex is the next index to get elements
int readIndex;
/ / next pointerLink next; }}Copy the code
Where Link has read and write indexes, inheriting AtomicInteger as write index, representing the index of elements that can be inserted next time; ReadIndex represents the subscript of the Elements array for the next available Handle.
There are two key methods for WeakOrderQueue. One is that the Add method stores Handle into Link, and the other is that the transfer method selects all handles in a Link and puts them into elements of the specified Stack.
WeakOrderQueue and Link have their own discarder strategies. The former is similar to Stack, while the latter is limited by maximum capacity Stack#availableSharedCapacity.
void add(DefaultHandle
handle) {
handle.lastRecycledId = id;
// Queue Discard policy
if (handleRecycleCount < interval) {
handleRecycleCount++;
return;
}
handleRecycleCount = 0;
Link tail = this.tail;
int writeIndex;
// If the tail Link is full, try creating a new Link
if ((writeIndex = tail.get()) == LINK_CAPACITY) {
// Link discard policy
Link link = head.newLink();
if (link == null) {
return;
}
this.tail = tail = tail.next = link;
writeIndex = tail.get();
}
tail.elements[writeIndex] = handle;
handle.stack = null;
tail.lazySet(writeIndex + 1);
}
Link newLink(a) {
return reserveSpaceForLink(availableSharedCapacity) ? new Link() : null;
}
/** * If availableSharedCapacity < LINK_CAPACITY(16), return false Discard * otherwise try availableSharedCapacity = availableSharedCapacity -link_capacity (16) */
static boolean reserveSpaceForLink(AtomicInteger availableSharedCapacity) {
for (;;) {
int available = availableSharedCapacity.get();
if (available < LINK_CAPACITY) {
return false;
}
if (availableSharedCapacity.compareAndSet(available, available - LINK_CAPACITY)) {
return true; }}}Copy the code
Transfer method is relatively long, the main logic is expansion + data migration.
boolean transfer(Stack
dst) {
Link head = this.head.link;
if (head == null) {
return false;
}
if (head.readIndex == LINK_CAPACITY) {
if (head.next == null) {
return false;
}
head = head.next;
this.head.relink(head);
}
final int srcStart = head.readIndex;
int srcEnd = head.get();
final int srcSize = srcEnd - srcStart;
if (srcSize == 0) {
return false;
}
final int dstSize = dst.size;
final int expectedCapacity = dstSize + srcSize;
// The DST capacity is insufficient
if (expectedCapacity > dst.elements.length) {
final int actualCapacity = dst.increaseCapacity(expectedCapacity);
srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd);
}
// Move DefaultHandle to elements in DST
if(srcStart ! = srcEnd) {final DefaultHandle[] srcElems = head.elements;
final DefaultHandle[] dstElems = dst.elements;
int newDstSize = dstSize;
for (inti = srcStart; i < srcEnd; i++) { DefaultHandle<? > element = srcElems[i];if (element.recycleId == 0) {
element.recycleId = element.lastRecycledId;
} else if(element.recycleId ! = element.lastRecycledId) {throw new IllegalStateException("recycled already");
}
srcElems[i] = null;
// The DST Stack discard policy may be triggered
if (dst.dropHandle(element)) {
// Drop the object.
continue;
}
element.stack = dst;
dstElems[newDstSize ++] = element;
}
if(srcEnd == LINK_CAPACITY && head.next ! =null) {
this.head.relink(head.next);
}
head.readIndex = srcEnd;
if (dst.size == newDstSize) {
return false;
}
dst.size = newDstSize;
return true;
} else {
return false; }}Copy the code
conclusion
-
How to use Netty object pool to create and recycle objects?
-
You need a constructor that takes objectPool.handle
private Item(ObjectPool.Handle<Item> handle) { this.handle = handle; } Copy the code
-
ObjectPool.ObjectCreator Netty provides ObjectPool.ObjectCreator newObject.
private static final ObjectPool<Item> pool = ObjectPool.newPool(new ObjectPool.ObjectCreator<Item>() { @Override public Item newObject(ObjectPool.Handle<Item> handle) { return newItem(handle); }});Copy the code
-
Provides a recycle method for target pooled objects that recycles the current instance through objectPool.handle.
public void recycle(a) { this.handle.recycle(this); } Copy the code
-
-
How does Netty abstract object pools?
- ObjectPool: ObjectPool, which provides the get method to obtain objects.
- ObjectCreator: ObjectCreator that provides the newObject method to create objects.
- Handle: Object collector that recycles objects using the Recycle method.
-
How does Netty implement object pooling?
- RecyclerObjectPool realization ObjectPool provides method to obtain objects; The ObjectCreator object creation logic is realized by the user. Recycler.DefaultHandle Recycles objects.
- Recycler is the inlet class of the ObjectPool. Each ObjectPool holds one Recycler.
private static final class RecyclerObjectPool<T> extends ObjectPool<T> { private final Recycler<T> recycler; RecyclerObjectPool(final ObjectCreator<T> creator) { recycler = new Recycler<T>() { @Override protected T newObject(Handle<T> handle) { returncreator.newObject(handle); }}; }@Override public T get(a) { returnrecycler.get(); }}Copy the code
- Thread pooling, where each thread holds a Stack pool of objects, reduces competition for resources and can be managed by Recycler.
// Recycler private finalFastThreadLocal<Stack<T>> threadLocal = ... ;Copy the code
-
How to handle the scenario where the same object is created and recycled on different threads?
- Premise: For an object pool RecyclerObjectPool (or Recycler) one thread (ThreadLocal) Stack
- Objects created and reclaimed by the current thread are stored directly in the Elements array of the Stack, where objects are retrieved first
- Objects created by the current thread can be recycled by other threads and stored in the Recycler’s ThreadLocal. The Map key is the Stack container that creates the Recycler object and the value holds objects recycled by the current thread. Each time an object is fetched, if the Elements array runs out, it is fetched from an object that another thread helps reclaim
// Recycler // Stack instances of other threads - A thread helps other threads' stack recycle objects in a WeakOrderQueue container private static finalFastThreadLocal<Map<Stack<? >, WeakOrderQueue>> DELAYED_RECYCLED =newFastThreadLocal<Map<Stack<? >, WeakOrderQueue>>() {@Override protectedMap<Stack<? >, WeakOrderQueue> initialValue() {return new WeakHashMap<Stack<?>, WeakOrderQueue>(); } }; Copy the code