Netty无锁化设计之对象池实现

池化技术是比较常见的一种技术,在平时我们已经就接触很多了,比如线程池,数据库连接池等等。当我们要使用一个资源的时候从池中去获取,用完就放回池中以便其他线程可以使用,这样的目的就是为了减少资源开销,提升性能。而Netty作为一个高性能的网络框架,在这一块也自然下足了工夫,下面我们就来看一下在Netty中的对象池是如何实现的吧

原理

        与其他池化实现不同的是,其他的池化实现都是全局的,但是这样的话在实现的过程中就可能会有并发的问题,比如说在获取资源以及回收资源的时候都需要通过加锁等手段去处理,而Netty的对象池为了避免这些问题,采用了ThreadLocal去为每一个线程创造一个对象池,这样的话每一个线程去获取对象以及回收对象的时候就只会在自己所属的对象池中去操作了,自然就避免了加锁的过程处理,简单来说就是通过空间换时间的思想,从而达到了无锁化的目的。

        不同的线程池使用独立的对象池虽然解决了上面加锁的问题,但是这也会导致另一个问题,比如说一个线程从自身的对象池中获取到了一个对象,但是这个对象被另外一个线程的对象池拿到并回收了,此时该对象就被回收到不属于自己的对象池中了。Netty为了解决这个问题,引入了一个队列,该队列就是专门存放这些帮助回收的线程回收的对象,举个例子,线程A从对象池中创建了一个对象,这个对象被线程B回收了,但是由于这个对象并不属于线程B的,所以线程B会为线程A创建一个队列,把这个回收对象放到这个队列中,当线程A再去从自身对象池中获取对象的时候,会先去这个队列中看是否有对象,如果有的话,就拿出来放回自身线程池中,这样就解决了上面的问题了。

        所以可以看到,Netty对象池中很多都是通过线程隔离的思想去避免线程间并发竞争的情况出现,完全体现出了无锁化的设计思想。

源码解析

(1)整体设计

Recycler
public abstract class Recycler<T> {protected abstract T newObject(Handle<T> handle);
}

Recycler是整个对象池的一个外壳,其中提供了一个newObject的抽象方法,主要就是给子类去进行实现的,子类在可以在该方法中去创建出对象池中的对象

stack
private static final class Stack<T> {// 省略部分代码/*** 存放handle的数组,也可以认为是存放对象的数组*/DefaultHandle<?>[] elements;// 省略部分代码
}
private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {@Overrideprotected Stack<T> initialValue() {return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,interval, maxDelayedQueuesPerThread, delayedQueueInterval);}@Overrideprotected void onRemoval(Stack<T> value) {// Let us remove the WeakOrderQueue from the WeakHashMap directly if its safe to remove some overheadif (value.threadRef.get() == Thread.currentThread()) {if (DELAYED_RECYCLED.isSet()) {DELAYED_RECYCLED.get().remove(value);}}}
};

stack其实就是真正的对象池实现,当每一个线程想去从stack中获取对象的时候,都会去从上面的FastThreadLocal中去获取到该线程自己的stack,然后再从stack中获取对象。stack中有一个数组,该数组就是真是存放对象的,但是存放的数据类型是一个DefaultHandle,那我们的对象哪里呢?其实我们的对象就是在DefaultHandle里面,这里DefaultHandle帮我们的对象做了一层包装。

Handle
public interface Handle<T> extends ObjectPool.Handle<T>  { }public abstract class ObjectPool<T> {public interface Handle<T> {void recycle(T self);}
}
private static final class DefaultHandle<T> implements Handle<T> {private static final AtomicIntegerFieldUpdater<DefaultHandle<?>> LAST_RECYCLED_ID_UPDATER;static {AtomicIntegerFieldUpdater<?> updater = AtomicIntegerFieldUpdater.newUpdater(DefaultHandle.class, "lastRecycledId");LAST_RECYCLED_ID_UPDATER = (AtomicIntegerFieldUpdater<DefaultHandle<?>>) updater;}/*** 当对象被其他帮助回收的线程回收了时候,该属性的值就是这个帮助回收的线程的id*/volatile int lastRecycledId;/*** 当原本创建该对象线程从其他帮助回收的线程中拿回该对象的时候,此时就会把lastRecycledId的值赋值给recycleId*/int recycleId;boolean hasBeenRecycled;Stack<?> stack;Object value;DefaultHandle(Stack<?> stack) {this.stack = stack;}@Overridepublic void recycle(Object object) {// 校验下回收的对象是否属于当前这个handleif (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);}public boolean compareAndSetLastRecycledId(int expectLastRecycledId, int updateLastRecycledId) {// Use "weak…" because we do not need synchronize-with ordering, only atomicity.// Also, spurious failures are fine, since no code should rely on recycling for correctness.return LAST_RECYCLED_ID_UPDATER.weakCompareAndSet(this, expectLastRecycledId, updateLastRecycledId);}
}

DefaultHandle类实现了Recycler中的Handle接口,而Recycler中的Handle接口又继承于ObjectPool的handle接口,在ObjectPool的handle接口中有一个recycle方法,该方法就是用来回收对象的,传入的参数就是要回收的对象,当调用recycle方法的时候,最终会把当前的handle放到对应的stack中,由此可以知道回收对象的入口是在handle,而不是stack。另外一个重点的地方就是DefaultHandle的value属性,它其实就是我们说所需要的原始对象,总体来看,stack与handle的关系如下图所示:

WeakOrderQueue

当其他线程帮忙回收对象的时候会把对象存放在哪里呢?答案就是在WeakOrderQueue中,一个帮忙回收的线程针对每一个stack都会有一个WeakOrderQueue去存放回收这个stack的对象,并且WeakOrderQueue中通过link指针去构造出一个link链表,所以一个WeakOrderQueue也就代表着一个link链表。同理,既然WeakOrderQueue是用来存放回收对象的,那么这些回收对象也需要被取走是吧,所以在一个stack中,也构造了一个WeakOrderQueue链表,表示当前这个stack被哪些线程的WeakOrderQueue回收了对象,当需要取这些回收对象的时候,此时就可以取遍历这个WeakOrderQueue即可

Link

一个link对象中包含了一个DefaultHandle数组,这个数组存放的就是帮助其他stack回收的对象,每一个link之间则形成了链表

小总结

所以综上所述,stack,handle,WeakOrderQueue与link这4者之间的关系如下图所示(引用网上的一张图):

最后,如果我们要使用Netty的对象池会怎么去使用呢?在Netty中提供了模板ObjectPool类去对Recycler进行了包装,以便于我们能够更方便地使用,ObjectPool类代码如下:

public abstract class ObjectPool<T> {ObjectPool() { }public abstract T get();public interface Handle<T> {void recycle(T self);}public interface ObjectCreator<T> {T newObject(Handle<T> handle);}public static <T> ObjectPool<T> newPool(final ObjectCreator<T> creator) {return new RecyclerObjectPool<T>(ObjectUtil.checkNotNull(creator, "creator"));}private static final class RecyclerObjectPool<T> extends ObjectPool<T> {private final Recycler<T> recycler;RecyclerObjectPool(final ObjectCreator<T> creator) {recycler = new Recycler<T>() {@Overrideprotected T newObject(Handle<T> handle) {return creator.newObject(handle);}};}@Overridepublic T get() {return recycler.get();}}
}

而ObjectPool可以如下使用:

ObjectPool<A> objectPool = ObjectPool.newPool(new ObjectPool.ObjectCreator<A>() {@Overridepublic A newObject(ObjectPool.Handle<A> handle) {return new A(handle);}
});A a = objectPool.get();
a.recycle();

当然了,我们创建的对象需要持有Handle,因为回收对象的方法是交给Handle去做的:

public class A {private final ObjectPool.Handle<A> handle;public A(ObjectPool.Handle<A> handle) {this.handle = handle;}public void recycle() {this.handle.recycle(this);}
}

(2)从stack中获取对象

DefaultHandle<T> pop() {// 获取到当前线程对应的stack中存在的对象的数量int size = this.size;// 条件成立:说明stack中没有对象if (size == 0) {// 此时需要去从WeakOrderQueue链表中获取对象,也就是从其他帮忙回收对象的线程的WeakOrderQueue中获取// 条件成立:表示其他帮忙回收对象的线程中也没有对象if (!scavenge()) {// 返回null,这样上层会创建新的对象return null;}size = this.size;if (size <= 0) {// double check, avoid racesreturn null;}}// 代码执行到这里说明此时stack中有存活的对象了,此时size数量-1size --;// 从数组中获取对象DefaultHandle ret = elements[size];elements[size] = null;// As we already set the element[size] to null we also need to store the updated size before we do// any validation. Otherwise we may see a null value when later try to pop again without a new element// added before.// 更新size数量this.size = size;if (ret.lastRecycledId != ret.recycleId) {throw new IllegalStateException("recycled multiple times");}// 重置这两个属性ret.recycleId = 0;ret.lastRecycledId = 0;// 返回对象return ret;
}

(3)其他线程帮忙回收对象

io.netty.util.Recycler.Stack#pushLater

如果代码来到这里,必定就是当前回收对象的线程与创建该对象的线程不是同一个线程

/*** 调用该方法的一定是帮助回收对象的线程。该方法会去把回收的对象放到对应的WeakOrderQueue中* @param item  回收的对象* @param thread    帮助回收对象的线程,也就是当前线程*/
private void pushLater(DefaultHandle<?> item, Thread thread) {if (maxDelayedQueues == 0) {// We don't support recycling across threads and should just drop the item on the floor.return;}// we don't want to have a ref to the queue as the value in our weak map// so we null it out; to ensure there are no races with restoring it later// we impose a memory ordering here (no-op on x86)// 获取这个帮助回收对象的线程对应的WeakOrderQueue,// key=>帮助哪个stack回收// value=>存放回收这个stack的对象的WeakOrderQueueMap<Stack<?>, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();WeakOrderQueue queue = delayedRecycled.get(this);// 条件成立:说明当前线程之前还没有帮助过这个stack回收对象if (queue == null) {// 条件成立:说明已经超过最大帮助这个stack回收对象的线程上限了if (delayedRecycled.size() >= maxDelayedQueues) {// 把需要帮助回收对象的stack作为key放到map中,对应的value是一个dummy的WeakOrderQueuedelayedRecycled.put(this, WeakOrderQueue.DUMMY);return;}// 代码执行到这里说明此时还没有超过最大帮助这个stack回收对象的线程上限// 给当前stack创建WeakOrderQueue// 条件成立:说明当前stack已经没有回收对象的数量去分配了,已经不能再创建新的WeakOrderQueue了if ((queue = newWeakOrderQueue(thread)) == null) {// 放弃回收这个对象return;}// 把stack和对应的WeakOrderQueue放到线程map中delayedRecycled.put(this, queue);}// 在上面的if中当超过最大帮助这个stack回收对象的线程上限的时候,就会给map中放入一个dummy的WeakOrderQueueelse if (queue == WeakOrderQueue.DUMMY) {// 放弃回收这个对象return;}// 把要回收的对象放到对应的WeakOrderQueue中queue.add(item);
}
/*** 每一个线程都对应一个map* key=>帮助哪个stack回收对象* value=>帮助这个stack回收对象时,存放对象的WeakOrderQueue*/
private static final FastThreadLocal<Map<Stack<?>, WeakOrderQueue>> DELAYED_RECYCLED =
new FastThreadLocal<Map<Stack<?>, WeakOrderQueue>>() {@Overrideprotected Map<Stack<?>, WeakOrderQueue> initialValue() {return new WeakHashMap<Stack<?>, WeakOrderQueue>();}
};

首先每个会去从FastThreadLocal中获取到一个map,map的key表示帮助的是哪一个stack去回收对象,value则是已经帮助这个stack回收的对象所存放的队列。

  • 如果该线程是第一次帮助这个stack去回收对象,那么就先判断下该线程已经帮助过多少个stack回收对象,如果此时已经达到了帮助上限,那么此次就不能帮助这个stack回收了,然后就给这个stack创建一个DUMMY类型的WeakOrderQueue,以便下一次该线程再帮助这个stack回收对象的时候发现对应的队列是一个WeakOrderQueue能够立刻放弃回收
  • 如果该线程不是第一次帮助这个stack回收对象了,那么能够获取到这个stack对应的WeakOrderQueue队列,反之则为这个stack创建一个新的WeakOrderQueue队列,并且把这个新创建的WeakOrderQueue队列放到map中,最后把要回收的对象放到WeakOrderQueue队列中
创建WeakOrderQueue队列

在上面回收的过程中,如果是第一次帮助这个stack回收,则需要为其创建一个新的WeakOrderQueue,代码如下:

private WeakOrderQueue newWeakOrderQueue(Thread thread) {
return WeakOrderQueue.newQueue(this, thread);
}
/*** 给指定的stack创建一个回收对象队列* 该方法可能会被并发调用,因为有可能此时会有多个帮助回收对象的线程在调用该方法去为指定的stack创建回收对象队列* @param stack 需要帮助回收对象的stack* @param thread    帮助回收对象的线程* @return  WeakOrderQueue对象*/
static WeakOrderQueue newQueue(Stack<?> stack, Thread thread) {// 条件成立:说明指定的stack已经没有回收对象的数量去分配给当前线程去创建回收对象队列了if (!Head.reserveSpaceForLink(stack.availableSharedCapacity)) {// 返回nullreturn null;}// 为stack创建一个新的WeakOrderQueuefinal WeakOrderQueue queue = new WeakOrderQueue(stack, thread);// 把这个新创建的WeakOrderQueue放到stack的WeakOrderQueue链表的头部stack.setHead(queue);// 返回这个新创建的WeakOrderQueuereturn queue;
}

可以看到,创建WeakOrderQueue之前需要判断对这个stack的availableSharedCapacity属性进行判断,那么这个属性是什么意思呢?其实就是这个stack有多少个对象是能够被其他线程帮助回收的,比方说100个,那么其他帮助回收的线程回收这个stack的对象加起来一共最多是100个

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;}}
}

由于可能会有多个帮助这个stack回收对象的线程同时去对availableSharedCapacity进行扣减,所以availableSharedCapacity这里通过AtomicInteger修饰,保证并发的情况下扣减正确

当创建完WeakOrderQueue之后,把这个WeakOrderQueue放到这个stack的WeakOrderQueue链表的头部

/*** 把指定的WeakOrderQueue放到链表的头部,头插法。因为有可能会有多个线程同时去给当前的stack创建回收对象线程,所以该方法需要加锁执行,* 同时这也是整个Netty对象池中唯一加锁的地方* @param queue 指定的WeakOrderQueue*/
synchronized void setHead(WeakOrderQueue queue) {queue.setNext(head);head = queue;
}

因为上面我们也说了,这里可能会有多个线程调用,也就是说会有多个线程去创建WeakOrderQueue加到stack的链表头部,所以这里需要进行加锁操作,需要注意的是这也是整个Netty对象池中唯一一处加锁的地方,因为整个Netty对象池的设计就是为了无锁化去设计的

往WeakOrderQueue添加回收对象
/*** 把要回收的handle对象放到当前的WeakOrderQueue的尾link节点中* @param handle    要回收的handle对象*/
void add(DefaultHandle<?> handle) {if (!handle.compareAndSetLastRecycledId(0, id)) {// Separate threads could be racing to add the handle to each their own WeakOrderQueue.// We only add the handle to the queue if we win the race and observe that lastRecycledId is zero.return;}// 这里是为了控制对象回收的频率,默认每8次回收才能回收1个对象if (!handle.hasBeenRecycled) {if (handleRecycleCount < interval) {handleRecycleCount++;// Drop the item to prevent from recycling too aggressively.return;}handleRecycleCount = 0;}// 获取到link链表中的尾节点Link tail = this.tail;// 当前link的写指针int writeIndex;// 条件成立:说明这个link已经放满对象了if ((writeIndex = tail.get()) == LINK_CAPACITY) {// 创建一个新的link节点Link link = head.newLink();// 条件成立:说明这个stack中没有可帮助回收的对象数量了if (link == null) {// 放弃回收这个对象return;}// 把新创建的link节点设置为尾节点this.tail = tail = tail.next = link;// 重置写指针writeIndex = tail.get();}// 把回收对象放到link节点中tail.elements[writeIndex] = handle;handle.stack = null;// we lazy set to ensure that setting stack to null appears before we unnull it in the owning thread;// this also means we guarantee visibility of an element in the queue if we see the index updated// 更新写指针tail.lazySet(writeIndex + 1);
}

WeakOrderQueue中是一个link节点的链表,每次添加回收对象的时候都是通过尾插法,获取到最后一个link节点的写指针,然后根据这个写指针把回收对象放入到这个link节点中。如果link节点已经放满了,那么就新创建出一个link节点(当然每次创建都要扣减availableSharedCapacity,如果扣减完了就返回null,此时就放弃回收这个对象了),并把这个link节点设置为尾节点,最后再把要回收的对象放入到这个新创建的link节点中

(4)从WeakOrderQueue中获取对象

io.netty.util.Recycler.Stack#scavengeSome

/*** 转移link链表中的对象,从头节点开始,如果这个link节点中的对象已经转移完了,就找下一个link节点进行转移,每调一次该方法就转移一个link节点* @return  当转移了一个link节点的时候返回true,反之返回false*/
private boolean scavengeSome() {// 前一个节点WeakOrderQueue prev;// 当前遍历到的节点WeakOrderQueue cursor = this.cursor;// 条件成立:说明此时是第一次遍历WeakOrderQueue链表if (cursor == null) {prev = null;cursor = head;// 条件成立:说明WeakOrderQueue为空,表示没有其他线程帮忙回收对象if (cursor == null) {return false;}}// 条件成立:不是第一次遍历WeakOrderQueue链表else {prev = this.prev;}boolean success = false;do {// 条件成立:说明转移了回收对象,此时跳出循环if (cursor.transfer(this)) {success = true;break;}// 代码执行到这里说明当前的WeakOrderQueue中没有转移到回收对象,此时获取下一个WeakOrderQueue节点WeakOrderQueue next = cursor.getNext();// 条件成立:说明这个WeakOrderQueue对象对应的线程挂了,也就是帮助回收的线程挂了if (cursor.get() == null) {// If the thread associated with the queue is gone, unlink it, after// performing a volatile read to confirm there is no data left to collect.// We never unlink the first queue, as we don't want to synchronize on updating the head.// 把这个WeakOrderQueue中剩余的link节点中的对象进行转移if (cursor.hasFinalData()) {for (;;) {// 每遍历一次就把这个WeakOrderQueue中的一个link节点的回收对象转移一次if (cursor.transfer(this)) {success = true;} else {break;}}}if (prev != null) {// 释放这个WeakOrderQueue所占用的回收对象的数量cursor.reclaimAllSpaceAndUnlink();// 把当前这个WeakOrderQueue从链表中删除prev.setNext(next);}} else {prev = cursor;}// 把下一个节点当作当前节点,继续遍历cursor = next;} while (cursor != null && !success);// 重新赋值prev和cursorthis.prev = prev;this.cursor = cursor;return success;
}

当我们想要从stack中获取对象时,首先会从WeakOrderQueue链表中获取,从上面代码也可以看到,通过上面的代码可以看到,cursor指针表示的是当前遍历到的WeakOrderQueue,如果这个WeakOrderQueue中的对象已经获取完了,那么就继续从下一个节点去获取,并更新cursor指针,那么这里又是怎样从WeakOrderQueue中获取对象的呢?具体逻辑在transfer方法中:

/*** 把当前WeakOrderQueue对象中的link链表的头节点中的全部可回收对象转移到指定的stack中* @param dst   指定的stack* @return  true=>有转移回收对象, false=>没有转移回收对象*/
@SuppressWarnings("rawtypes")
boolean transfer(Stack<?> dst) {Link head = this.head.link;// 条件成立:说明该WeakOrderQueue对象中还没有link链表if (head == null) {return false;}// 条件成立:说明当前头节点已经把回收对象全部转移完了if (head.readIndex == LINK_CAPACITY) {// 如果头节点的下一个节点为null,就说明没有回收对象可转移了if (head.next == null) {return false;}// 把下一个节点设置为头节点head = head.next;this.head.relink(head);}// 获取到当前的读指针final int srcStart = head.readIndex;// 获取到这个link节点中存放的回收对象数量int srcEnd = head.get();// 两者相减得到的就是需要转移的回收对象数量final int srcSize = srcEnd - srcStart;// 条件成立:说明没有可回收的对象能够转移if (srcSize == 0) {return false;}// 获取到转移目标的stack中的存活对象数量final int dstSize = dst.size;// 计算出当把可回收对象全部转移到stack中时,stack所需的大小final int expectedCapacity = dstSize + srcSize;// 条件成立:说明如果把可回收对象全部转移到stack中的时候,stack需要扩容if (expectedCapacity > dst.elements.length) {// 对stack进行扩容,得到扩容后的stack大小final int actualCapacity = dst.increaseCapacity(expectedCapacity);// actualCapacity - dstSize得到的就是stack扩容之后剩余的大小// 因为stack扩容之后有可能还是完全放不下link中可转移的回收对象,所以这里取最小值srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd);}// 条件成立:说明link节点中存在可转移的回收对象if (srcStart != srcEnd) {// 获取到link节点中的对象数组final DefaultHandle[] srcElems = head.elements;// 获取到转移目标stack中的对象数组final DefaultHandle[] dstElems = dst.elements;int newDstSize = dstSize;// 遍历link节点的对象数组的可回收对象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)) {// 放弃回收该对象continue;}// 代码执行到这里说明该对象能够被回收// 重新给handle中的stack属性赋值element.stack = dst;// 把对象放到stack的对象数组中,此时就完成了该对象的回收dstElems[newDstSize ++] = element;}// 条件成立:说明这个link已经把可回收对象全部转移完了,此时把下一个link节点设置为头节点if (srcEnd == LINK_CAPACITY && head.next != null) {// Add capacity back as the Link is GCed.this.head.relink(head.next);}// 把可回收的对象转移完了之后更新读指针head.readIndex = srcEnd;// 条件成立:说明此次没有转移到一个回收对象if (dst.size == newDstSize) {return false;}// 代码执行到这里说明此次转移到至少一个回收对象,此时更新stack的sizedst.size = newDstSize;return true;} else {// The destination stack is full already.return false;}
}

我们从上面已经知道,每一个WeakOrderQueue对象其实就是一个由link节点组成的链表,而每一个link节点中存储对象靠的就是一个数组。当调用transfer方法的时候,首先会获取当前link链表的头节点,根据读指针和写指针计算出这个link节点转移对象的数组开始下标和结束下标,然后根据开始下标和结束下标把数组中的对象转移到stack中,最后判断这个link节点是否已经全部转移完里面的对象了,如果已经转移完了,就把下一个link节点置为头节点,这样当下一次再调用transfer方法的时候就能够又能从头节点获取了。也就是说每调用一次transfer方法,就会转移最多一个link节点数量的对象,如果一个对象都没有转移到,那么transfer方法就返回false,反之返回true。

分析了transfer方法,我们再回到scavengeSome方法中重新进行分析,在scavengeSome方法中大概的执行流程如下:

  • 首先会通过cursor指针定位到已经遍历到的WeakOrderQueue对象,然后调用这个WeakOrderQueue对象的transfer方法进行对象转移
  • 如果transfer方法返回true,则跳出do...while循环,反之如果返回false,则获取下一个WeakOrderQueue,并赋值给cursor指针,然后do...while循环重复上面第一点的步骤
  • 如果transfer返回false,则说明这一次没有转移到对象,此时会再去判断当前这个WeakOrderQueue对应的线程是否已经挂了,如果挂了则再判断WeakOrderQueue中是否还存在没有被转移的link节点(因为有可能由于回收频率的控制导致这个link节点中本来有的对象被放弃回收了,但是该节点后可能还存在其他没有被转移的link节点),如果存在,则把剩余的这些link节点中的对象全部转移到stack中,最后再把这个WeakOrderQueue占用的回收对象数量归还给availableSharedCapacity属性,并且把这个WeakOrderQueue从链表中删除

总结来说scavengeSome方法会去遍历WeakOrderQueue链表,如果当前WeakOrderQueue转移不到对象,就换下一个WeakOrderQueue节点进行转移,当发现有WeakOrderQueue节点转移对象成功之后就返回true,反之返回false

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/454749.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL-23.多表查询-内连接

一.内连接 -- 多表查询 select * from tb_emp,tb_dept where tb_emp.dept_id tb_dept.id;-- 内连接 -- A.查询员工的姓名&#xff0c;及所属的部门名称&#xff08;隐式内连接实现&#xff09; select tb_emp.name as 员工姓名,tb_dept.name as 部门名称 from tb_emp,tb_dep…

简单介绍冯诺依曼体系

现代的计算机, 大多遵守冯诺依曼体系结构 CPU中央处理器&#xff1a;进行算术运算和逻辑判断。存储器&#xff1a;分为外存和内存&#xff0c;用于存储数据&#xff08;使用二进制方式存储&#xff09;。输入设备&#xff1a;用户给计算机发号施令。输出设备&#xff1a;计算机…

RISC-V笔记——Pipeline依赖

1. 前言 RISC-V的RVWMO模型主要包含了preserved program order、load value axiom、atomicity axiom、progress axiom和I/O Ordering。今天主要记录下preserved program order(保留程序顺序)中的Pipeline Dependencies(Pipeline依赖)。 2. Pipeline依赖 Pipeline依赖指的是&a…

LeetCode_2520. 统计能整除数字的位数_java

1、题目 2520. 统计能整除数字的位数https://leetcode.cn/problems/count-the-digits-that-divide-a-number/ 给你一个整数 num &#xff0c;返回 num 中能整除 num 的数位的数目。 如果满足 nums % val 0 &#xff0c;则认为整数 val 可以整除 nums 。 示例 1&#xff1a;…

TiDB替换Starrocks:业务综合宽表迁移的性能评估与降本增效决策

作者&#xff1a; 我是人间不清醒 原文来源&#xff1a; https://tidb.net/blog/6638f594 1、 场景 业务综合宽表是报表生成、大屏幕展示和数据计算处理的核心数据结构。目前&#xff0c;这些宽表存储在Starrocks系统中&#xff0c;但该系统存在显著的性能瓶颈。例如&#…

如何实现金蝶商品数据集成到电商系统的SKU

如何实现金蝶商品数据集成到电商SKU系统 金蝶商品数据集成到电商SKU的技术实现 在现代企业的数据管理中&#xff0c;系统间的数据对接与集成是提升业务效率和准确性的关键环节。本文将分享一个实际案例&#xff1a;如何通过轻易云数据集成平台&#xff0c;将金蝶云星辰V2中的商…

实战华为AC6508无线控制器+华为无线AP上线配置(AirEngine5762S-12+AirEngine5760-10)+无线WIFI配置

一、适用场景 1、适用于企业环境、校园环境、大户型家庭多层楼环境。 2、对于无线网络需要集中管理和监测的环境&#xff0c;无线wifi覆盖范围面积大&#xff0c;适用本实例。 3、当无线WIFI需要从一个区域到另一个区域无缝漫游时&#xff0c;确保应用不掉线&#xff0c;可使用…

简单有效修复d3d9.dll错误,11种d3d9.dll错误详细解决办法教程

当你遇到d3d9.dll文件丢失的问题时&#xff0c;可以通过今天的这篇文章详细的步骤来尝试修复这个问题&#xff0c;今天将教大家十一种d3d9.dll丢失修复的方法。 1. 重新安装DirectX以恢复d3d9.dll d3d9.dll是DirectX的一部分&#xff0c;因此重新安装DirectX通常可以解决d3d9.…

C#描述-计算机视觉OpenCV(7):MSER特征检测

C#描述-计算机视觉OpenCV&#xff08;7&#xff09;&#xff1a;MSER特征检测 基本概念操作实例效果优化 基本概念 前文C#描述-计算机视觉OpenCV&#xff08;6&#xff09;&#xff1a;形态学描述了如何对图像的前后景特征形态进行检测与运算&#xff0c;本篇将分析基于形态的…

Safari 中 filter: blur() 高斯模糊引发的性能问题及解决方案

目录 引言问题背景&#xff1a;filter: blur() 引发的问题产生问题的原因分析解决方案&#xff1a;开启硬件加速实际应用示例性能优化建议常见的调试工具与分析方法 引言 在前端开发中&#xff0c;CSS滤镜&#xff08;如filter: blur()&#xff09;的广泛使用为页面带来了各种…

大数据-173 Elasticsearch 索引操作 增删改查 详细 JSON 操作

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

后台管理员登录实现--系统篇

我的小系统后台原来就有一个上传图片的功能还夹带个删除图片的功能&#xff0c;还嵌到了一个菜单里面。之前效果如下 那么现在为了加大安全力度&#xff0c;想增加一个登录页面。通过登录再到这个页面。看着貌似很简单&#xff0c;但是听我细细说来&#xff0c;要新增些什么东西…

KMP 算法

目录 KMP 算法 算法思路 为什么不需要在主串中进行回退 计算 next 数组 代码实现 next 数组优化 查找所有起始位置 KMP 算法 KMP 算法是一种改进的字符串匹配算法&#xff0c;由 D.E.Knuth&#xff0c;J.H.Morris 和 V.R.Pratt 提出的&#xff0c;因此人们称它为 克努特…

(北京政务服务满意度公司)满意度调查助力服务质量提升

在当今社会&#xff0c;&#xff08;政务服务满意度公司&#xff09;政务窗口服务的质量直接关系到市民的日常生活和城市的健康发展。为了解市民对政务窗口服务的满意度&#xff0c;提升服务质量&#xff0c;某市委托民安智库专业市场调查公司开展了政务窗口服务满意度调查&…

【平方矩阵 + 蛇形矩阵】

矩阵找规律题 题目链接&#xff1a; 平方矩阵 I平方矩阵 II平方矩阵 III蛇形矩阵 平方矩阵 I 解法一&#xff1a;找坐标规律 while True:x int(input())if not x:breakfor i in range(x):for j in range(x):print(%d % min(i 1, j 1, x - i, x - j), end )print()prin…

【Hive】3-HiveSQL 数据定义语言(DDL)

HiveSQL 数据定义语言&#xff08;DDL&#xff09; SQL中DDL语法的作用 数据定义语言(Data Definition Language&#xff0c;DDL)&#xff0c;是SQL语言集中对数据库内部的对象结构进行创建&#xff0c;删除&#xff0c;修改等的操作语言&#xff0c;这些数据库对象包括datab…

SpringBoot实现的汽车票在线预订系统

2相关技术 2.1 MySQL 数据库 MySQL 是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非…

5G NR GSCN计算SSB中心频率MATLAB实现

本期给大家带来5G NR中已知GSCN如何计算SSB的中心频率&#xff0c;用MATLAB实现&#xff0c;参考3GPP 38.104 下图是GSCN与SSB中心频率换算关系。 函数说明&#xff1a; 函数的入参是GSCN号 函数的输出是对应的SSB中心频率&#xff0c;单位MHZ function freqency nr_5g_gs…

汽配企业数字工厂管理系统实施规划方案

在当今快速发展的汽车工业中&#xff0c;汽配企业面临着日益激烈的市场竞争和不断变化的客户需求。为了提升生产效率、优化资源配置并增强市场竞争力&#xff0c;实施数字工厂管理系统已成为汽配企业转型升级的关键举措。本方案旨在提出一套全面、可行的数字工厂管理系统实施规…

U盘文件或目录损坏且无法读取:原因、恢复与预防全攻略

一、U盘文件或目录损坏现状概览 U盘&#xff0c;作为我们日常生活中不可或缺的数据存储设备&#xff0c;其便捷性和实用性广受好评。然而&#xff0c;在使用U盘的过程中&#xff0c;不少用户都曾遇到过一个棘手的问题——U盘文件或目录损坏且无法读取。这一故障不仅会导致数据…