BufferRecyclerPool.java
package com.fasterxml.jackson.core.util;
import java.io.Serializable;
import java.util.Deque;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicReference;
/**
* Interface for entity that controls creation and possible reuse of {@link BufferRecycler}
* instances used for recycling of underlying input/output buffers.
*<p>
* Different pool implementations use different strategies on retaining
* recyclers for reuse. For example we have:
*<ul>
* <li>{@link NonRecyclingPool} which does not retain any recyclers and
* will always simply construct and return new instance when {@code acquireBufferRecycler}
* is called
* </li>
* <li>{@link ThreadLocalPool} which uses {@link ThreadLocal} to retain at most
* 1 recycler per {@link Thread}.
* </li>
* <li>{@link BoundedPool} is "bounded pool" and retains at most N recyclers (default value being
* {@link BoundedPool#DEFAULT_CAPACITY}) at any given time.
* </li>
* <li>Two implementations -- {@link ConcurrentDequePool}, {@link LockFreePool}
* -- are "unbounded" and retain any number of recyclers released: in practice
* it is at most the highest number of concurrently used {@link BufferRecycler}s.
* </li>
*</ul>
*
*<p>
* Default implementations are also included as nested classes.
*
* @since 2.16
*/
public interface BufferRecyclerPool extends Serializable
{
/**
* Method called to acquire {@link BufferRecycler}; possibly
* (but necessarily) a pooled recycler instance (depends on implementation
* and pool state).
*
* @return {@link BufferRecycler} for caller to use; caller expected
* to call {@link #releaseBufferRecycler} after it is done using recycler.
*/
BufferRecycler acquireBufferRecycler();
/**
* Method that should be called when previously acquired (see {@link #acquireBufferRecycler})
* recycler instances is no longer needed; this lets pool to take ownership
* for possible reuse.
*
* @param recycler
*/
void releaseBufferRecycler(BufferRecycler recycler);
/**
* @return the default {@link BufferRecyclerPool} implementation
* which is the thread local based one:
* basically alias to {@link #threadLocalPool()}).
*/
static BufferRecyclerPool defaultPool() {
return threadLocalPool();
}
/**
* @return Globally shared instance of {@link ThreadLocalPool}; same as calling
* {@link ThreadLocalPool#shared()}.
*/
static BufferRecyclerPool threadLocalPool() {
return ThreadLocalPool.shared();
}
/**
* @return Globally shared instance of {@link NonRecyclingPool}; same as calling
* {@link NonRecyclingPool#shared()}.
*/
static BufferRecyclerPool nonRecyclingPool() {
return NonRecyclingPool.shared();
}
/*
/**********************************************************************
/* Default BufferRecyclerPool implementations
/**********************************************************************
*/
/**
* Default {@link BufferRecyclerPool} implementation that uses
* {@link ThreadLocal} for recycling instances. {@link BufferRecycler}
* instances are stored using {@link java.lang.ref.SoftReference}s so that
* they may be Garbage Collected as needed by JVM.
*<p>
* Note that this implementation may not work well on platforms where
* {@link java.lang.ref.SoftReference}s are not well supported (like
* Android), or on platforms where {@link java.lang.Thread}s are not
* long-living or reused (like Project Loom).
*/
class ThreadLocalPool implements BufferRecyclerPool
{
private static final long serialVersionUID = 1L;
private static final BufferRecyclerPool GLOBAL = new ThreadLocalPool();
/**
* Accessor for the global, shared instance of {@link ThreadLocal}-based
* pool: due to its nature it is essentially Singleton as there can only
* be a single recycled {@link BufferRecycler} per thread.
*
* @return Shared pool instance
*/
public static BufferRecyclerPool shared() {
return GLOBAL;
}
// No instances beyond shared one should be constructed
private ThreadLocalPool() { }
// // // JDK serialization support
protected Object readResolve() { return GLOBAL; }
// // // Actual API implementation
@SuppressWarnings("deprecation")
@Override
public BufferRecycler acquireBufferRecycler() {
return BufferRecyclers.getBufferRecycler();
}
@Override
public void releaseBufferRecycler(BufferRecycler recycler) {
; // nothing to do, relies on ThreadLocal
}
}
/**
* {@link BufferRecyclerPool} implementation that does not use
* any pool but simply creates new instances when necessary.
*/
class NonRecyclingPool implements BufferRecyclerPool
{
private static final long serialVersionUID = 1L;
private static final BufferRecyclerPool GLOBAL = new NonRecyclingPool();
// No instances beyond shared one should be constructed
private NonRecyclingPool() { }
/**
* Accessor for the shared singleton instance; due to implementation having no state
* this is preferred over creating instances.
*
* @return Shared pool instance
*/
public static BufferRecyclerPool shared() {
return GLOBAL;
}
// // // JDK serialization support
protected Object readResolve() { return GLOBAL; }
// // // Actual API implementation
@Override
public BufferRecycler acquireBufferRecycler() {
// Could link back to this pool as marker? For now just leave back-ref empty
return new BufferRecycler();
}
@Override
public void releaseBufferRecycler(BufferRecycler recycler) {
; // nothing to do, there is no underlying pool
}
}
/**
* Intermediate base class for instances that are stateful and require
* special handling with respect to JDK serialization, to retain
* "global" reference distinct from non-shared ones.
*/
abstract class StatefulImplBase implements BufferRecyclerPool {
private static final long serialVersionUID = 1L;
protected final static int SERIALIZATION_SHARED = -1;
protected final static int SERIALIZATION_NON_SHARED = 1;
/**
* Value that indicates basic aspects of pool for JDK serialization;
* either marker for shared/non-shared, or possibly bounded size;
* depends on sub-class.
*/
protected final int _serialization;
protected StatefulImplBase(int serialization) {
_serialization = serialization;
}
protected Optional<BufferRecyclerPool> _resolveToShared(BufferRecyclerPool shared) {
if (_serialization == SERIALIZATION_SHARED) {
return Optional.of(shared);
}
return Optional.empty();
}
}
/**
* {@link BufferRecyclerPool} implementation that uses
* {@link ConcurrentLinkedDeque} for recycling instances.
*<p>
* Pool is unbounded: see {@link BufferRecyclerPool} what this means.
*/
class ConcurrentDequePool extends StatefulImplBase
{
private static final long serialVersionUID = 1L;
private static final ConcurrentDequePool GLOBAL = new ConcurrentDequePool(SERIALIZATION_SHARED);
private final transient Deque<BufferRecycler> pool;
// // // Life-cycle (constructors, factory methods)
protected ConcurrentDequePool(int serialization) {
super(serialization);
pool = new ConcurrentLinkedDeque<>();
}
/**
* Accessor for getting the globally shared singleton instance.
* Note that if you choose to use this instance,
* pool may be shared by many other {@code JsonFactory} instances.
*
* @return Shared pool instance
*/
public static ConcurrentDequePool shared() {
return GLOBAL;
}
/**
* Accessor for creating and returning a new, non-shared pool instance.
*
* @return Newly constructed, non-shared pool instance
*/
public static ConcurrentDequePool nonShared() {
return new ConcurrentDequePool(SERIALIZATION_NON_SHARED);
}
// // // JDK serialization support
/**
* Make sure to re-link to global/shared or non-shared.
*/
protected Object readResolve() {
return _resolveToShared(GLOBAL).orElseGet(() -> nonShared());
}
// // // Actual API implementation
@Override
public BufferRecycler acquireBufferRecycler() {
BufferRecycler bufferRecycler = pool.pollFirst();
if (bufferRecycler == null) {
bufferRecycler = new BufferRecycler();
}
return bufferRecycler.withPool(this);
}
@Override
public void releaseBufferRecycler(BufferRecycler bufferRecycler) {
pool.offerLast(bufferRecycler);
}
}
/**
* {@link BufferRecyclerPool} implementation that uses
* a lock free linked list for recycling instances.
* Pool is unbounded: see {@link BufferRecyclerPool} for
* details on what this means.
*/
class LockFreePool extends StatefulImplBase
{
private static final long serialVersionUID = 1L;
/**
* Globally shared pool instance.
*/
private static final LockFreePool GLOBAL = new LockFreePool(SERIALIZATION_SHARED);
// Needs to be transient to avoid JDK serialization from writing it out
private final transient AtomicReference<LockFreePool.Node> head;
// // // Life-cycle (constructors, factory methods)
private LockFreePool(int serialization) {
super(serialization);
head = new AtomicReference<>();
}
/**
* Accessor for getting the globally shared singleton instance.
* Note that if you choose to use this instance,
* pool may be shared by many other {@code JsonFactory} instances.
*
* @return Shared pool instance
*/
public static LockFreePool shared() {
return GLOBAL;
}
/**
* Accessor for creating and returning a new, non-shared pool instance.
*
* @return Newly constructed, non-shared pool instance
*/
public static LockFreePool nonShared() {
return new LockFreePool(SERIALIZATION_NON_SHARED);
}
// // // JDK serialization support
/**
* Make sure to re-link to global/shared or non-shared.
*/
protected Object readResolve() {
return _resolveToShared(GLOBAL).orElseGet(() -> nonShared());
}
// // // Actual API implementation
@Override
public BufferRecycler acquireBufferRecycler() {
return _getRecycler().withPool(this);
}
private BufferRecycler _getRecycler() {
// This simple lock free algorithm uses an optimistic compareAndSet strategy to
// populate the underlying linked list in a thread-safe way. However, under very
// heavy contention, the compareAndSet could fail multiple times, so it seems a
// reasonable heuristic to limit the number of retries in this situation.
for (int i = 0; i < 3; i++) {
Node currentHead = head.get();
if (currentHead == null) {
return new BufferRecycler();
}
if (head.compareAndSet(currentHead, currentHead.next)) {
currentHead.next = null;
return currentHead.value;
}
}
return new BufferRecycler();
}
@Override
public void releaseBufferRecycler(BufferRecycler bufferRecycler) {
LockFreePool.Node newHead = new LockFreePool.Node(bufferRecycler);
for (int i = 0; i < 3; i++) {
newHead.next = head.get();
if (head.compareAndSet(newHead.next, newHead)) {
return;
}
}
}
private static class Node {
final BufferRecycler value;
LockFreePool.Node next;
Node(BufferRecycler value) {
this.value = value;
}
}
}
/**
* {@link BufferRecyclerPool} implementation that uses
* a bounded queue ({@link ArrayBlockingQueue} for recycling instances.
* This is "bounded" pool since it will never hold on to more
* {@link BufferRecycler} instances than its size configuration:
* the default size is {@link BoundedPool#DEFAULT_CAPACITY}.
*/
class BoundedPool extends StatefulImplBase
{
private static final long serialVersionUID = 1L;
/**
* Default capacity which limits number of recyclers that are ever
* retained for reuse.
*/
public final static int DEFAULT_CAPACITY = 100;
private static final BoundedPool GLOBAL = new BoundedPool(SERIALIZATION_SHARED);
private final transient ArrayBlockingQueue<BufferRecycler> pool;
private final transient int capacity;
// // // Life-cycle (constructors, factory methods)
protected BoundedPool(int capacityAsId) {
super(capacityAsId);
capacity = (capacityAsId <= 0) ? DEFAULT_CAPACITY : capacityAsId;
pool = new ArrayBlockingQueue<>(capacity);
}
/**
* Accessor for getting the globally shared singleton instance.
* Note that if you choose to use this instance,
* pool may be shared by many other {@code JsonFactory} instances.
*
* @return Shared pool instance
*/
public static BoundedPool shared() {
return GLOBAL;
}
/**
* Accessor for creating and returning a new, non-shared pool instance.
*
* @param capacity Maximum capacity of the pool: must be positive number above zero.
*
* @return Newly constructed, non-shared pool instance
*/
public static BoundedPool nonShared(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("capacity must be > 0, was: "+capacity);
}
return new BoundedPool(capacity);
}
// // // JDK serialization support
/**
* Make sure to re-link to global/shared or non-shared.
*/
protected Object readResolve() {
return _resolveToShared(GLOBAL).orElseGet(() -> nonShared(_serialization));
}
// // // Actual API implementation
@Override
public BufferRecycler acquireBufferRecycler() {
BufferRecycler bufferRecycler = pool.poll();
if (bufferRecycler == null) {
bufferRecycler = new BufferRecycler();
}
return bufferRecycler.withPool(this);
}
@Override
public void releaseBufferRecycler(BufferRecycler bufferRecycler) {
pool.offer(bufferRecycler);
}
// // // Other methods
public int capacity() {
return capacity;
}
}
}