JVM的垃圾清除算法,众所周知,在年轻代用的都是 复制清除算法,这也就是为什么年轻代分为了S0和S1 两个大小一样的内存空间。 S0满了,就复制到S1去,然后清空S0,当然,GC还会整理好内存,使其连续起来,那么还有哪些常用软件或框架用到了这个双缓冲机制呢?
1 MySql的Redo Log
我们知道,在InnoDB存储引擎的数据目录下会有两个名为ib_logfile0和ib_logfile1的文件,这
就是InnoDB的重做日志文件(redo log file),它记录了对于InnoDB存储引擎的事务日志。
重做日志文件的作用是什么?
当InnoDB的数据存储文件发生错误时,重做日志文件就能派上用场。InnoDB存储引擎可以使用重
做日志文件将数据恢复为正确状态,以此来保证数据的正确性和完整性。
为了得到更高的可靠性,用户可以设置多个镜像日志组,将不同的文件组放在不同的磁盘上,以此
来提高重做日志的高可用性。
重做日志文件组是如何写入数据的?
每个InnoDB存储引擎至少有1个重做日志文件组(group),每个文件组下至少有2个重做日志文件,如默
认的ib_logfile0和ib_logfile1。
在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。
InnoDB存储引擎先写入重做日志文件1,当文件被写满时,会切换到重做日志文件2,再当重做日
志文件2也被写满时,再切换到重做日志文件1。
2 Redis的对象类型之Hash:一个hashtable由1个dict结构、2个dictht结构、1个dictEntry指针数组(称为bucket)和多
个dictEntry结构组成。
正常情况下(即hashtable没有进行rehash时)各部分关系如下图所示
问题来了,为什么有两个dictht呢?一个不就行了么?
dictht是一个包含两个项的数组,每项都指向一个dictht结构,这也是Redis的哈希会有1个dict、2个dictht结构的原因。通常情况下,所有的数据都是存在放dict的ht[0]中,ht[1]只在rehash的时候使用。dict进行rehash操作的时候,将ht[0]中的所有数据rehash到ht[1]中。然后将ht[1]赋值给ht[0],并清空ht[1]
3 hadoop的namenode落盘元数据
hadoop的namenode在处理目录树结点元数据时,是可以承受的住多线程高并发的压力的,那他是怎么做到的呢?也是双缓冲机制,当A缓存块满了,切换为B缓存块,同时A缓存块进行落盘操作
双缓冲java落盘代码:
package hadoop;
import java.util.LinkedList;
public class FSEditLog {
//构建元数据信息类--editlog
public class Editlog{
public long txid ; //事务id
public String log ; //元数据信息
public Editlog(long txid, String log) {
this.txid = txid;
this.log = log;
}
@Override
public String toString() {
return "Editlog{" +
"txid=" + txid +
", log='" + log + '\'' +
'}';
}
}
public class DoubleBuf{
LinkedList<Editlog> bufCurrent = new LinkedList<>();
LinkedList<Editlog> bufReady = new LinkedList<>();
//写元数据信息到第一块内存
public void write(Editlog editlog){
bufCurrent.add(editlog);
}
//刷元数据操作
public void flush(){
for(Editlog editlog:bufReady){
System.out.println("当前刷盘的元数据是:"+editlog.toString());
}
bufReady.clear();
}
//交换内存
public void swap(){
//bufCurrrent ---> bufReady
LinkedList<Editlog> tmp = bufReady ;
bufReady = bufCurrent ;
bufCurrent = tmp ;
}
//获取最大事务ID
public long getMaxtxid(){
return bufReady.getLast().txid ;
}
}
private long txid = 0;
DoubleBuf doubleBuf = new DoubleBuf();
ThreadLocal<Long> threadLocal = new ThreadLocal<Long>();
public void writeEditlog(String log){
synchronized (this){//锁开始
txid ++ ;
threadLocal.set(txid);
//创建元数据
Editlog editlog = new Editlog(txid , log);
doubleBuf.write(editlog);
}//锁结束
logFlush();
}
public boolean isSyncRunning = false ; //是否正在刷盘
long maxTxid = 0 ;//当前正在运行的最大事务id
//如果threadLocal.get() >= maxTxid
//说明这个是新的任务 , 但是当前任务正在运行,需要等待,否则会出现线程不安全
boolean isWait = false;//是否等待
//刷磁盘操作
public void logFlush(){
synchronized (this){//锁开始
if(isSyncRunning){
if(threadLocal.get() < maxTxid){//为了保证顺序,必须是最大的事务id
//说明,当前线程的工作已经被其他线程完成
return;
}
if(isWait){
return ;
}
isWait = true ;
while (isSyncRunning){//线程1正在刷盘 , 线程2过来了 , 需要等待
try {
this.wait(1000);//如果有线程进来发现正在处于同步状态,那么等待1s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isWait = false ;//如果上面刷盘结束了,就不需要等待了,所以改变状态
}
//如果此时没有正在刷盘,那么交换内存
doubleBuf.swap();
// maxTxid = doubleBuf.getMaxtxid() ;
if(doubleBuf.bufReady.size()>0){
maxTxid = doubleBuf.getMaxtxid() ;
}
//交换内存之后,具备了刷盘条件
isSyncRunning = true ;
}//锁结束
doubleBuf.flush();//到这一步为止 , bufReady里面的数据已经刷完了
synchronized (this){
isSyncRunning = false ;//说明线程1已经刷盘结束了
this.notifyAll();
}
}
public static void main(String[] args) {
final FSEditLog fsEditlog = new FSEditLog();
boolean isFlag = true ;
long taxid = 0 ;
while (isFlag){
taxid ++ ;
if(taxid == 500){
isFlag = false;
}
new Thread(new Runnable() {
boolean isFlag_2 = true ;
long a = 0 ;
public void run() {
while (isFlag_2){
a ++ ;
if(a == 100){
isFlag_2 = false ;
}
//
fsEditlog.writeEditlog("元数据");
}
}
}).start();
}
}
}