直接贴代码
package com.asiainfo.custom.doublebuff;
import java.util.LinkedList;
/**
* 总结,采取了分段锁,将(获取事务id->写内存数据)(刷盘)进行分段锁。就是说,
* 多线程顺序写数据。使得可以多线程批量刷数据。
* 如果不使用双缓冲,那么读写就只能在一块内存中
* 如果不使用分段锁,那么就只能串行刷新一条数据。
*/
public class DoubleBuff {
public LinkedList<EditLog> currentMemory = new LinkedList<>();
public LinkedList<EditLog> buffReady = new LinkedList<>();
public void write(EditLog editLog) {
currentMemory.add(editLog);
}
public void exChangeMemory() {
LinkedList<EditLog> tmp = null;
tmp = this.currentMemory;
this.currentMemory = this.buffReady;
this.buffReady = tmp;
}
public void flush() {
for (EditLog editLog : buffReady) {
//模拟将数据写到磁盘
System.out.println(editLog);
}
buffReady.clear();
}
public int getMaxTxid() {
return buffReady.getLast().getTxid();
}
}
class LogManager {
private DoubleBuff doubleBuff = new DoubleBuff();
private int txid = 0;
private ThreadLocal<Integer> curPro = new ThreadLocal<>();
//需要通过这个方法写内存.
//这个方法是多线程调用的。
public void logEdit(String logContent) throws InterruptedException {
synchronized (this) { //锁一
EditLog editLog = new EditLog(logContent, txid);
//多线程调用的,所以这个++ 就肯定会出现加错的情况了
txid++;
//把当前现成,跟事务id绑定在一起;现成A->1 B->2 C->3
curPro.set(txid);
doubleBuff.write(editLog);
}
//这个操作是非常耗时的,上面的write又很快,所以会有大量的线程都聚集到这个flush中
logFlush();
}
//false代表正在刷盘
private boolean isSyncRunning = false;
private int maxTxid = 0;
private boolean isWait = false;
private void logFlush() throws InterruptedException {
/* thinking...
* 交换内存是需要线程安全的操作,所以这里加锁,不能每个线程都同时交换内存。
* 但是我们不希望,写内存->交换空间 这两个步骤串行操作。因为如果串行操作了,那么就只会每次
* 有一条数据写入current 紧接着就开始交换内存。这样效率可能不高
*/
//所以刷盘的时候,有可能内存里面已经有好多事务的数据了。锁二
synchronized (this) {
/* thinking...
* 这里思考,那我们进来,可以直接就交换内存吗(current里面是有数据的)
* 显然是不可以的。如果我们交换内存的时候,有线程正在刷盘,那么就会发生错误
* 所以我们应该找一个标志位,当刷盘结束的时候,改变状态,所以我们设立 isSyncRunning
*/
//如果正在刷盘,那么就是没操作也不进行
if (isSyncRunning) {
/* thinking...
* 如果我当前的现成事务id<正在处理的事务id,说明这个数据已经被刷盘了
* 由于其他线程率先抢到了锁二,而这个线程的事务日志比当前的大,而却已经刷盘了
* 所以当前现成就什么也不需要做了
*/
if (curPro.get() < maxTxid) {
return;
}
/*
* 如果有线程睡眠要执行任务了,那么也不需要其他线程处理了,那就退出。
*/
if (isWait) {
return;
}
//进入到这里的代码都是curPro.get()>=maxTxid,说明,当前现成写入的数据还没有被处理
//那么当前现成是肯定要执行的,那就先给当前现成睡眠,等待刷盘完成
//但是wait方法会让出锁,所以其他线程也会拿到锁进入这个方法,
isWait = true;
while (isSyncRunning) {
wait(100);
}
isWait = false;
}
doubleBuff.exChangeMemory();
if (doubleBuff.buffReady.size() > 0) {
maxTxid = doubleBuff.getMaxTxid();
}
}
doubleBuff.flush();
synchronized (this) {
isSyncRunning = true;
this.notify();
}
}
}