在L2聚合后要落库的过程中,有一个读写安全缓存,所以简单分析一下:
/**
* Pointer of read buffer.
*/
private volatile BufferedData<T> readBufferPointer;
/**
* Pointer of write buffer.
*/
private volatile BufferedData<T> writeBufferPointer;
/**
* Read/Write lock.
*/
private final ReentrantLock lock;
/**
* Build the Cache through two given buffer instances.
*
* @param buffer1 read/write switchable buffer
* @param buffer2 read/write switchable buffer. It is the write buffer at the beginning.
*/
public ReadWriteSafeCache(BufferedData<T> buffer1, BufferedData<T> buffer2) {
readBufferPointer = buffer1;
writeBufferPointer = buffer2;
lock = new ReentrantLock();
}
/**
* Write the into the {@link #writeBufferPointer} buffer.
*
* @param data to enqueue.
*/
public void write(T data) {
lock.lock();
try {
writeBufferPointer.accept(data);
} finally {
lock.unlock();
}
}
/**
* Write the collection of data into the {@link #writeBufferPointer} buffer.
*
* @param data to enqueue.
*/
public void write(List<T> data) {
lock.lock();
try {
data.forEach(writeBufferPointer::accept);
} finally {
lock.unlock();
}
}
public List<T> read() {
lock.lock();
try {
// Switch the read and write pointers, when there is no writing.
BufferedData<T> tempPointer = writeBufferPointer;
writeBufferPointer = readBufferPointer;
readBufferPointer = tempPointer;
} finally {
lock.unlock();
}
// Call read method outside of write lock for concurrency read-write.
return readBufferPointer.read();
}
可以看到有两个指向,一个是读缓存一个是写缓存,并不是单纯的两个指针(即两者指向的对象不同),并发情况下用volatile修饰保证可见性;
可以看到读写都需要抢锁,并且都是独占式,但是明显读更快,因为读方法在加锁的范围内只进行了指向的交换,并且读是一个拿出数据并清除缓存的过程(相当于取,即只取一次);
前提是读请求定时执行,不存在竞争,因此只需要保证读写两者不冲突即可;
public List<METRICS> read() {
try {
return buffer.values().stream().collect(Collectors.toList());
} finally {
buffer.clear();//读完即清除,保证只被读取一次
}
}
这才是交换指向的意义,举个例子:
1.加锁 写空集合 释放锁
2 加锁 追加写 释放锁
3 加锁 交换指针(读有内容) 释放锁 读 清除内容