双缓冲机制在一些软件或框架内的应用

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();
    }
}

}

上一篇:汽车Tbox介绍


下一篇:Android P 连接tbox 后热点无法打开的问题