Caffeine中,读操作后的afterRead操作都会异步操作,不会阻塞到正常的读取操作。
在高并发读取的前提下,为每个读取操作的线程建立了专属的buffer来存放afterRead事件由消费者统一处理afterRead事件,避免高并发读取下对于事件写入buffer的时候的资源竞争。
transient volatile Buffer<E>[] table;
StrippedBuffer中维护了一个Buffer[]数组table,用来存放各个线程专属的buffer。
如何界定某个buffer属于哪个线程去写入,又或者如何判断线程在写入的时候应该选择哪个buffer。
static final long PROBE = UnsafeAccess.objectFieldOffset(Thread.class, "threadLocalRandomProbe");
static final int getProbe() {
return UnsafeAccess.UNSAFE.getInt(Thread.currentThread(), PROBE);
}
在为每个线程选择具体的buffer的时候,将会根据每个线程的ThreadLocalRandom来hash定位buffer数组的具体位置,每次需要定位的时候通过getProbe()方法,通过Unsafe来获取Thread的threadLocalRandomProbe随机数,通过hash的方式进行定位。
@Override
public int offer(E e) {
int mask;
int result = 0;
Buffer<E> buffer;
boolean uncontended = true;
Buffer<E>[] buffers = table;
if ((buffers == null)
|| (mask = buffers.length - 1) < 0
|| (buffer = buffers[getProbe() & mask]) == null
|| !(uncontended = ((result = buffer.offer(e)) != Buffer.FAILED))) {
expandOrRetry(e, uncontended);
}
return result;
}
当事件具体准备通过offer()方法根据线程的ThreadLocal随机数选择到具体的buffer数组table上的位置,并进行写入。
其中,可能会出现buffer数组没初始化,buffer数组对应位置上的buffer还没初始化完成,buffer由于hash碰撞被并发写入导致一个线程的写入失败,几种情况,这几种情况会通过expandOrRetry()方法进行重试或者扩容。
transient volatile int tableBusy;
final boolean casTableBusy() {
return UnsafeAccess.UNSAFE.compareAndSwapInt(this, TABLE_BUSY, 0, 1);
}
在扩容过程中,通过cas修改tablrBusy的量保证线程安全。
本文深入探讨了Caffeine缓存库的读写机制,特别是其如何利用线程本地缓冲区和异步处理提高高并发场景下的读取效率,通过ThreadLocalRandom实现buffer的定位,确保读取操作的快速响应。
657

被折叠的 条评论
为什么被折叠?



