/**
* Copyright 2013, Landz and its contributors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package z.util.concurrent;
import z.util.primitives.Ints;
import java.util.function.Supplier;
import static z.util.Contracts.contract;
import static z.util.Unsafes.currentThreadId;
/**
*
* ThreadLocalPool, is a pool for type T. This may be good for "heavy" object.
* But, ThreadLocalPool, as a "heavy" object container, itself is "heavy". And,
* this ThreadLocalPool does not support "recycle". Once, you need to recycle
* pooled objects, you may need to recycle often. Then, the sense of pool
* has been gone.
* <p>
* The better way is to estimate the maximum cursor of your ThreadLocalPool.
* However, you would get null when no object is available at calling point.
* You should check against this if needed.
* <p>
* Ideas:
* 1. too large pool is still not friendly to GC;
*
* <p>
* Note:
* this ThreadLocalPool instance only support the long-living Thread
* usage now, although this case may be improved in the future when some low level
* hooking ready.
*
*/
public class ThreadLocalPool<T> implements AutoCloseable {
private static final int MAX_SUPPORTED_THREAD_ID = 1024;//FIXME
//TODO: false sharing
private final ItemStack[] tlStacks = new ItemStack[MAX_SUPPORTED_THREAD_ID];
private final int incrementStep;
private final int capacity;
private final Supplier<T> supplier;//return supplier.get();
public ThreadLocalPool(int capacity, Supplier<T> supplier) {
this(capacity, capacity, supplier);
}
public ThreadLocalPool(int capacity, int incrementStep, Supplier<T> supplier) {
contract(
() -> Ints.isPowerOfTwo(capacity)
&& Ints.isPowerOfTwo(incrementStep)
&& (incrementStep<=capacity) ,
() -> new IllegalArgumentException("Now the capacity " +
"is supported to be the power of 2 only."));//FIXME
this.capacity = capacity;
this.incrementStep = incrementStep;
this.supplier = supplier;
}
/**
* return one pooled item which contains type T object, or return null
* immediately if no item is available.
*/
public Item<T> item() {
int tid = (int)currentThreadId();
//FIXME as contract
if (tid==MAX_SUPPORTED_THREAD_ID)
throw new IllegalStateException(
"the supported threadId should be less than "
+MAX_SUPPORTED_THREAD_ID);
ItemStack stack = tlStacks[tid];
if (stack==null) {
stack = new ItemStack(incrementStep, incrementStep!=capacity, supplier);
tlStacks[tid] = stack;
}
if (stack.cursor ==0 && stack.notMaxCapacity) {
if (capacity<=(stack.capacity+incrementStep)) {
stack.enlarge(incrementStep);
if (capacity==stack.capacity)
stack.notMaxCapacity = false;
}
}
return stack.pop();
}
@Override
/**
* release the pool's all resources
*/
public void close() throws Exception {
for (int i = 0; i < tlStacks.length; i++) {
ItemStack stack = tlStacks[i];
if (stack!=null) {
Item<T>[] items = stack.items;
for (int j = 0; j < items.length; j++) {
Object obj = items[j].get();
if (obj instanceof AutoCloseable)
((AutoCloseable)obj).close();
items[j] = null;
}
tlStacks[i] = null;
}
}
}
public static class Item<T> implements AutoCloseable {
T object;
final ItemStack stack;
Item(T object, ItemStack stack) {
this.object = object;
this.stack = stack;
}
public T get() {
return object;
}
@Override
public void close() {
stack.push(this);
}
}
private static class ItemStack<T> {
private final Supplier supplier;
private Item<T>[] items;
private int capacity;
private int cursor;
boolean notMaxCapacity;
ItemStack(int initSize, boolean notMaxCapacity, Supplier<T> supplier) {
this.supplier = supplier;
this.notMaxCapacity = notMaxCapacity;
enlarge(initSize);
}
Item<T> pop() {
if (cursor == 0) {
return null;
}
return items[--cursor];
}
void push(Item item) {
items[cursor++] = item;
}
void enlarge(int incrementStep) {
int newCapacity = capacity+incrementStep;
Item<T>[] newItems = new Item[newCapacity];
if (items!=null) {
System.arraycopy(items, 0, newItems, capacity, incrementStep);
}
items = newItems;
capacity = newCapacity;
cursor += incrementStep;
//initialize the empty slot
for (int i = 0; i < incrementStep; i++) {
items[i]=new Item(supplier.get(), this);
}
}
}
}