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的量保证线程安全。