根据老师的课程 模仿Hadoop写的
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicLong;
/**
负责管理edits log日志的核心组件
@author
@create 2021-10-28 17:30
*/
public class FSEditlog {
/**
* 当前递增到的txid的序号
*/
private volatile long txidSeq = 0L;
private DoubleBuffer editLogsBuffer = new DoubleBuffer();
/**
* 当前是否在将内存缓冲刷入磁盘中
*/
private volatile Boolean isSyncRunning = false;
/**
* 当前是否有线程在等待刷新下一批edits log到磁盘里去
*/
private volatile Boolean isWaitSync = false;
/**
* 每个线程自己本地的txid副本
*/
private ThreadLocal<Long> localTxid = new ThreadLocal<Long>();
/**
* 在同步到磁盘中的最大的一个txid
*/
private volatile Long syncMaxTxid = 0L;
/**
* todo 核心流程方法, 提现notify和wait的用法的地方
* 记录edits log日志
* @param content
*/
public void logEdit(String content) {
synchronized (this) {
txidSeq++;
long txid = txidSeq;
localTxid.set(txid);
editLogsBuffer.write(new EditLog(0L, content));
}
logSync();
}
private void logSync() {
synchronized (this) {
if (isSyncRunning) {
Long txid = localTxid.get();
if (txid <= syncMaxTxid) {
System.out.printf("t=%s,txid=%s, max=%s\n", Thread.currentThread().getName(), txid, syncMaxTxid);
return;
}
// 此时再来一个txid = 9的线程的话,那么他会发现说,已经有线程在等待刷下一批数据到磁盘了
// 此时他会直接返回
// 假如说此时来一个txid = 6的线程,那么的话,他是不好说的
// 他就需要做一些等待,同时要释放掉锁
if (isWaitSync) {
return;
}
isWaitSync = true;
while (isSyncRunning) {
try {
wait(200);
} catch (Exception e) {
e.printStackTrace();
}
}
isWaitSync = false;
}
// 交换缓冲区
editLogsBuffer.setReadyToSync();
// 能走到这里的肯定是最牛逼那个
// 保存当前要同步到磁盘中去的最大的txid
// 此时editLogBuffer中的syncBuffer这块区域, 交换完以后这里可能有多条数据
// 而且他里边的editLog的txid一定是从小到大的
syncMaxTxid = editLogsBuffer.syncBuffer.getLast().txid;
isSyncRunning = true;
}
// 开始同步内存缓冲的数据到磁盘中去
// 这个过程很慢, 基本上毫秒级别, 弄不好几十毫秒
editLogsBuffer.flush();
synchronized (this) {
// 同步完成磁盘, 将标记位复位, 再释放锁
isSyncRunning = false;
// 唤醒可能正在等待他同步完磁盘的线程
notifyAll();
}
}
/**
* 内存双缓冲
*/
class DoubleBuffer {
/**
* 专门用于承载写入edits log
*/
LinkedList<EditLog> currentBuffer = new LinkedList<EditLog>();
/**
* 专门用于将数据同步到磁盘中去的一块缓冲
*/
LinkedList<EditLog> syncBuffer = new LinkedList<EditLog>();
public void write(EditLog log) {
currentBuffer.add(log);
}
public void setReadyToSync() {
LinkedList<EditLog> tmp = currentBuffer;
syncBuffer = tmp;
currentBuffer = new LinkedList<EditLog>();
}
// 老师的写法 这种写法省内存多一点
// /**
// * 交换两块缓冲区,为了同步内存数据到磁盘做准备
// */
// public void setReadyToSync() {
// LinkedList<EditLog> tmp = currentBuffer;
// currentBuffer = syncBuffer;
// syncBuffer = tmp;
// }
/**
* 将syncBuffer缓冲区中的数据刷入磁盘中
*/
public AtomicLong batch = new AtomicLong(0L);
public void flush() {
for (EditLog log : syncBuffer) {
System.out.println("将 edit log 写入磁盘文件中: " + log+"=="+batch);
}
syncBuffer.clear();
batch.incrementAndGet();
}
}
/**
* 代表了一条edits log
* @author
*
*/
class EditLog {
long txid;
String content;
public EditLog(long txid, String content) {
this.txid = txid;
this.content = content;
}
}
}
test程序
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
@author
@create 2021-11-04 22:04
*/
public class TSEditLogTest {
private static final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(100);
private static final CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
FSEditlog fsEditlog = new FSEditlog();
for (int i = 0; i < 100; i++) {
THREAD_POOL.submit(() -> {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.printf("t=%s\n",Thread.currentThread().getName());
fsEditlog.logEdit(UUID.randomUUID().toString());
});
}
latch.countDown();
Thread.sleep(1000);
System.out.println("============");
}
}