这次的Recycler 改动很大
去掉了之前用的stack,链表,weakReference等,详细请见 https://github.com/netty/netty/pull/11858
- ok,Netty的Recycler 的设计原理,就是需要对象的时候取,不需要把对象存起来。
启到复用的效果存起来,怎么存,取,从哪里取。做一个怎样的集合,要不要考虑线程安全。 - 对,接下来就是结合新版本来解答下面这些问题。
线程安全。获得新对象 - 先简单的看下这段代码,里面有threadLocal 来为每个线程存放一个集合localPool。
这里就是利用threadLocal来保障线程安全。
在从localPool中获取Handle。这里的Handle有两个作用,一个是用来绑定新对象,一个是用来绑定localPool,目的是用绑定的localPool,存当前的Handle。
//Recycler类的get方法
@SuppressWarnings("unchecked")
public final T get() {
if (maxCapacityPerThread == 0) {
return newObject((Handle<T>) NOOP_HANDLE);
}
//从threadLocal 获取一个localPool
LocalPool<T> localPool = threadLocal.get();
//从localPool获取一个Handle。
DefaultHandle<T> handle = localPool.claim();
T obj;
//如果handle是null
if (handle == null) {
// 创建新handle,并绑定此localPool。
handle = localPool.newHandle();
if (handle != null) {
//创建新对象,对需要复用的对象。一般而言,新对象也会有个成员变量来存Handle,
//目的是之后利用Handle的方法,将新对象绑定的Handle存到localPool中
obj = newObject(handle);
//绑定对象。
handle.set(obj);
} else {
obj = newObject((Handle<T>) NOOP_HANDLE);
}
} else {
//直接从hanle中获取value
obj = handle.get();
}
return obj;
}
- 再来看存,刚刚再上段代码就写到了,存,是通过handle来存的,没错。
就是简单的从绑定好的localPool中,把自己又存进去,因为handle已经绑定了对象。 - 需要注意的是,localPool本身的线程安全问题。因为存对象时,没有了ThreadLocal,就会发生多个线程同时调用存操作,这时候,就需要localPool本身需要是线程安全的。
@Override
public void recycle(Object object) {
if (object != value) {
throw new IllegalArgumentException("object does not belong to handle");
}
//把自己存起来
localPool.release(this);
}
//set 就是将对象放在成员变量中。
void set(T value) {
this.value = value;
}
- 这里还需要引出一个小问题,就是handle如果多次操作recycle().总不能将同一个handle不断的存入localPool吧,比如:get()时创建了一个handle,handle再多次操作recycle()方法后,又多次调用get(),就有可能多次得到同样的handle,那么之后的操作就不可控了。所以,Handle中有一个state来标识是否存入了localPool。
private static final class DefaultHandle<T> implements Handle<T> {
private static final int STATE_CLAIMED = 0;
private static final int STATE_AVAILABLE = 1;
private static final AtomicIntegerFieldUpdater<DefaultHandle<?>> STATE_UPDATER;
static {
AtomicIntegerFieldUpdater<?> updater = AtomicIntegerFieldUpdater.newUpdater(DefaultHandle.class, "state");
//noinspection unchecked
STATE_UPDATER = (AtomicIntegerFieldUpdater<DefaultHandle<?>>) updater;
}
@SuppressWarnings({"FieldMayBeFinal", "unused"}) // Updated by STATE_UPDATER.
private volatile int state; // State is initialised to STATE_CLAIMED (aka. 0) so they can be released.
//将存入的数据,state转为claim
boolean availableToClaim() {
if (state != STATE_AVAILABLE) {
return false;
}
return STATE_UPDATER.compareAndSet(this, STATE_AVAILABLE, STATE_CLAIMED);
}
//将state标识为available
void toAvailable() {
int prev = STATE_UPDATER.getAndSet(this, STATE_AVAILABLE);
if (prev == STATE_AVAILABLE) {
throw new IllegalStateException("Object has been recycled already.");
}
}
而在localPool也有体现,state转变的调用
private static final class LocalPool<T> {
private final int ratioInterval;
private volatile MessagePassingQueue<DefaultHandle<T>> pooledHandles;
private int ratioCounter;
@SuppressWarnings("unchecked")
LocalPool(int maxCapacity, int ratioInterval, int chunkSize) {
this.ratioInterval = ratioInterval;
if (BLOCKING_POOL) {
pooledHandles = new BlockingMessageQueue<DefaultHandle<T>>(maxCapacity);
} else {
pooledHandles = (MessagePassingQueue<DefaultHandle<T>>) newMpscQueue(chunkSize, maxCapacity);
}
ratioCounter = ratioInterval; // Start at interval so the first one will be recycled.
}
//取对象,
DefaultHandle<T> claim() {
MessagePassingQueue<DefaultHandle<T>> handles = pooledHandles;
if (handles == null) {
return null;
}
DefaultHandle<T> handle;
do {
handle = handles.relaxedPoll();
// 循环的条件是 handle 不为null 且 handle转为claim()不成功或hanle为available
} while (handle != null && !handle.availableToClaim());
return handle;
}
// 将对象存入localpool
void release(DefaultHandle<T> handle) {
MessagePassingQueue<DefaultHandle<T>> handles = pooledHandles;
//设置handle的state为available
handle.toAvailable();
if (handles != null) {
handles.relaxedOffer(handle);
}
}
//调节存数据的频率
DefaultHandle<T> newHandle() {
if (++ratioCounter >= ratioInterval) {
ratioCounter = 0;
return new DefaultHandle<T>(this);
}
//只有DefaultHandle 才能存对象到localPool中
return null;
}
}
- 之前强调过的localPool本身的线程安全问题就是看你使用哪种MessagePassingQueue,又两种选择,一种是Jctools的队列,一种是ArrayDeque,ArrayDeque在poll() offer() 都用synchronized 来保证线程安全。
//MessagePassingQueue 如果是ArrayDeque
@Override
public synchronized boolean offer(T e) {
if (deque.size() == maxCapacity) {
return false;
}
return deque.offer(e);
}
@Override
public synchronized T poll() {
return deque.poll();
}
@Override
public synchronized T peek() {
return deque.peek();
}
最后就是
就是ThreadLocal要清除自己的值,就需要将LocalPool清除掉。
private final FastThreadLocal<LocalPool<T>> threadLocal = new FastThreadLocal<LocalPool<T>>() {
@Override
protected LocalPool<T> initialValue() {
return new LocalPool<T>(maxCapacityPerThread, interval, chunkSize);
}
//清除
@Override
protected void onRemoval(LocalPool<T> value) throws Exception {
super.onRemoval(value);
MessagePassingQueue<DefaultHandle<T>> handles = value.pooledHandles;
value.pooledHandles = null;
handles.clear();
}
};
总结下,recycler 可以达到对象复用的效果,但是并不严格保证每个对象都需要复用,localPool也有自己的最大容量,且ratioInterval 也可调节(这部分代码在Local Pool newHandle() 中体现)。但是在复用的过程中,看到了线程安全的考量。较之前的版本,更容易理解、没那么复杂了。