Recycler的作用是你在创建对象的时候不需要每次都通过new的方式去创建,如果这里面有已经用过的对象,那么可以直接取出来进行二次利用,当你不需要这个对象的时候就可以将他放到这里面,等之后需要再使用这个对象的时候就直接取出来进行二次利用。
public class RecycleTest {
private static final Recycler<User> RECYCLER = new Recycler<User>() {
@Override
protected User newObject(Handle<User> handle) {
return new User(handle);
}
};
private static class User {
private final Recycler.Handle<User> handle;
public User(Recycler.Handle<User> handle) {
this.handle = handle;
}
public void recycle() {
handle.recycle(this);
}
}
public static void main(String[] args) {
User user = RECYCLER.get();
user.recycle();
RECYCLER.get().recycle();
User user1 = RECYCLER.get();
System.out.println(user1 == user);
}
}
这是我们本节要分析的代码,首先我们创建了一个RECYCLER对象,里面我们实现了一个方法newObject,这个方法是用来当对象池中没有可以再利用的对象的时候就再创建一个,并且我们传入了一个handle对象,他就是用来回收对象的。
private final int maxCapacityPerThread;
private final int maxSharedCapacityFactor;
private final int ratioMask;
private final int maxDelayedQueuesPerThread;
private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
@Override
protected Stack<T> initialValue() {
return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,
ratioMask, maxDelayedQueuesPerThread);
}
};
我们发现没有给recycler里面有一个threadLocal,里面保存了一个stack,这个stack就是用来存储我们的回收的对象的,我们看一下它的构造方法:
Stack(Recycler<T> parent, Thread thread, int maxCapacity, int maxSharedCapacityFactor,
int ratioMask, int maxDelayedQueues) {
this.parent = parent;
当前线程
this.thread = thread;
//回收的对象的最大数目
this.maxCapacity = maxCapacity;
//能够再其他线程里面缓存的最大个数
availableSharedCapacity = new AtomicInteger(max(maxCapacity / maxSharedCapacityFactor, LINK_CAPACITY));
//存储的回收的对象
elements = new DefaultHandle[min(INITIAL_CAPACITY, maxCapacity)];
//回收的频率
this.ratioMask = ratioMask;
this.maxDelayedQueues = maxDelayedQueues;
}
从Recycler的当前线程里面获取对象,分为一下三个部分:
1、获取当前线程的Stack
2、从Stack里面弹出对象
3、创建对象并绑定
下面我们就对照这这三个步骤来看一下源码的实现:
public final T get() {
//如果线程对应的最大容量为0,直接返回
if (maxCapacityPerThread == 0) {
return newObject((Handle<T>) NOOP_HANDLE);
}
//得到当前线程的Stack
Stack<T> stack = threadLocal.get();
//尝试从stack里面弹出一个handle
DefaultHandle<T> handle = stack.pop();
//如果为空,说明还没有回收的对象,需要新创建一个对象
if (handle == null) {
handle = stack.newHandle();
handle.value = newObject(handle);
}
//返回获取到的对象
return (T) handle.value;
}
对象的创建和回收都是通过handle来实现的。
接下来我们看一下回收对象到Recycler,一共有两种方式
1、同线程回收对象
public void recycle(Object object) {
if (object != value) {
throw new IllegalArgumentException("object does not belong to handle");
}
stack.push(this);
}
首先他会调用stack的push方法:
void push(DefaultHandle<?> item) {
Thread currentThread = Thread.currentThread();
if (thread == currentThread) {
// The current Thread is the thread that belongs to the Stack, we can try to push the object now.
pushNow(item);
} else {
// The current Thread is not the one that belongs to the Stack, we need to signal that the push
// happens later.
pushLater(item, currentThread);
}
}
然后根据是否是当前stack绑定的线程就是当前的线程,我们先来看一下同线程的:
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 (size >= maxCapacity || dropHandle(item)) {
// Hit the maximum capacity or should drop - drop the possibly youngest object.
return;
}
//扩容
if (size == elements.length) {
elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
}
//放到数组里面
elements[size] = item;
this.size = size + 1;
}
逻辑还是很简单的,首先会判断是否超过了最大容量或者应该不回收,如果都是false,继续向下执行,判断是否需要扩容,如果需要,那么就将原来的数组的内容复制到一个空间是原来两倍的数组里面,然后将回收的对象放到stack里面。
2、异线程回收对象
这个过程分为下面三个步骤:
1、获取WeakOrderQueue
2、如果获取到的WeakOrderQueue为null,那么就创建一个WeakOrderQueue
3、将对象追加到WeakOrderQueue
接下来我们就对着源码来看一下:
private void pushLater(DefaultHandle<?> item, Thread thread) {
// 首先会获得当前线程的delayedRecycled ,这个map里面是其他线程的stack对应的WeakOrderQueue
Map<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();
//获得其他线程的WeakOrderQueue
WeakOrderQueue queue = delayedRecycled.get(this);
//如果为null,那么就新创建一个
if (queue == null) {
//判断当前线程的delayedRecycled已经持有的其他线程的WeakOrderQueue已经大于最大的数量了,那么就做一个标记,代表,不回收当前stack的对象
if (delayedRecycled.size() >= maxDelayedQueues) {
// Add a dummy queue so we know we should drop the object
delayedRecycled.put(this, WeakOrderQueue.DUMMY);
return;
}
// 创建一个WeakOrderQueue
if ((queue = WeakOrderQueue.allocate(this, thread)) == null) {
// drop object
return;
}
delayedRecycled.put(this, queue);
//如果被标记为不回收当前stack的对象就直接返回
} else if (queue == WeakOrderQueue.DUMMY) {
// drop object
return;
}
//将当前回收的对象放到queue里面
queue.add(item);
}
每一个线程都对应着一个WeakOrderQueue结点,他和其他线程的结点通过链表的方式连接起来,每个结点里面也是一个链表,这个链表里面就是存储handle的,称为Link,每个Link里面有一个数组,可以存放多个handle。
接下来我们看一下如何从异线程中收割对象:
boolean scavenge() {
// continue an existing scavenge, if any
if (scavengeSome()) {
return true;
}
// reset our scavenge cursor
prev = null;
cursor = head;
return false;
}
首先回尝试从其他线程中回收属于当前线程的对象,根据有没有回收到返回一个标志:
boolean scavengeSome() {
//得到当前所指向的Link
WeakOrderQueue cursor = this.cursor;
//如果cursor为空,那么让他重新指向头节点
if (cursor == null) {
cursor = head;
//如果头节点还是null,说明其他线程中没有保存的当前线程的对象
if (cursor == null) {
return false;
}
}
boolean success = false;
//得到当前Link的前一个对象
WeakOrderQueue prev = this.prev;
//通过循环将其他线程中保存的对象返回到当前stack中
do {
if (cursor.transfer(this)) {
success = true;
break;
}
//得到下一个Link
WeakOrderQueue next = cursor.next;
//如果当前结点的存在的线程已经不存在了,要做一些清理的工作
if (cursor.owner.get() == null) {
//如果当前结点还有对象,需要把对象都回收过来
if (cursor.hasFinalData()) {
for (;;) {
if (cursor.transfer(this)) {
success = true;
} else {
break;
}
}
}
if (prev != null) {
prev.next = next;
}
} else {
prev = cursor;
}
cursor = next;
} while (cursor != null && !success);
this.prev = prev;
this.cursor = cursor;
return success;
}
我们具体来看一下transfer方法:
boolean transfer(Stack<?> dst) {
//得到当前stack保存的头节点
Link head = this.head;
//如果为空直接返回
if (head == null) {
return false;
}
判断头节点的读指针是否到了一个Link的最大容量,如果到达了,需要移动到下一个Link上
if (head.readIndex == LINK_CAPACITY) {
if (head.next == null) {
return false;
}
this.head = head = head.next;
}
//得到当前头节点的读指针
final int srcStart = head.readIndex;
//得到当前结点的容量
int srcEnd = head.get();
//得到存储的对象的数目
final int srcSize = srcEnd - srcStart;
//如果数目为0直接返回
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 = min(srcStart + actualCapacity - dstSize, srcEnd);
}
//如果读指针和可以回收的最后一个对象的下不相等,可以进行回收
if (srcStart != srcEnd) {
//得到当前Link结点的数组
final DefaultHandle[] srcElems = head.elements;
//得到stack的数组
final DefaultHandle[] dstElems = dst.elements;
int newDstSize = dstSize;
//简单的赋值
for (int i = 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;
//判断是否需要丢弃
if (dst.dropHandle(element)) {
// Drop the object.
continue;
}
element.stack = dst;
//放到stack里面
dstElems[newDstSize ++] = element;
}
//当前Link是否用完了
if (srcEnd == LINK_CAPACITY && head.next != null) {
// Add capacity back as the Link is GCed.
reclaimSpace(LINK_CAPACITY);
this.head = head.next;
}
head.readIndex = srcEnd;
if (dst.size == newDstSize) {
return false;
}
dst.size = newDstSize;
return true;
} else {
// The destination stack is full already.
return false;
}
}
这个方法看着很长,其实解释简单的两个数组的复制。
Netty中的两个性能优化工具就介绍完了,用最简单的数据结构实现了很大程度上的优化。