【Java】进程通信(共享内存)

博客主页:我的主页
欢迎点赞 收藏 留言 欢迎讨论!
本文由 【泠青沼~】 原创,首发于 CSDN
由于博主是在学小白一枚,难免会有错误,有任何问题欢迎评论区留言指出,感激不尽!个人主页


目录

  • 一、核心要点
    • 1.1、MappedByteBuffer
    • 1.2、 FileChannel
    • 1.3、 RandomAccessFile
  • 二、Java类的实现
    • 2.1、写进程(加锁)
    • 2.2、读进程(加锁)
    • 2.3、运行读写进程
    • 2.4、写进程(不加锁)
    • 2.4、读进程(不加锁)


一、核心要点

1.1、MappedByteBuffer

Java IO 操作的BufferedReaderBufferedInputStream 等相信大家都很熟悉,不过在 Java NIO 中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高。

MappedByteBuffer 为共享内存缓冲区,实际上是一个磁盘文件的内存映射,实现内存与文件的同步变化,可有效地保证共享内存的实现。

1.2、 FileChannel

FileChannel 是将共享内存和磁盘文件建立联系的文件通道类。FileChannel 类的加入是JDK为了统一对外设备(文件、网络接口等)的访问方法,并加强了多线程对同一文件进行存取的安全性。我们在这里用它来建立共享内存和磁盘文件间的一个通道。

1.3、 RandomAccessFile

RandomAccessFileJava IO体系中功能最丰富的文件内容访问类,它提供很多方法来操作文件,包括读写支持,与普通的IO流相比,它最大的特别之处就是支持任意访问的方式,程序可以直接跳到任意地方来读写数据。

举个例子:

如果我们要向已存在的大小为 1G 的 txt 文本里末尾追加一行文字,内容如下“ 你好,我是小明”。其实直接使用Java 中的流读取 txt 文本里所有的数据转成字符串后,然后拼接“你好,我是小明”,又写回文本即可。
但如果需求改了,我们要想向大小为 8G 的 txt 文本里追加数据。如果我们电脑的内存只有 4G ,强制读取所有的数据并追加,将会报内存溢出的异常。显然,上面的方法不再合适。
如果我们使用 JAVA IO 体系中的 RandomAccessFile类来完成的话,可以实现零内存追加。其实,这就是支持任意位置读写类的强大之处。

二、Java类的实现

2.1、写进程(加锁)

/**
 * create by dong on 2023/5/8
 */
public class NIOWriteLock {
    private static RandomAccessFile raf;
    public static void main(String[] args) throws Exception {
        //获取随机存取文件对象,建立文件和内存的映射,即时双向同步
        raf = new RandomAccessFile("E:/temp/source.dat", "rw");
        FileChannel fc = raf.getChannel();      //获取文件通道
        MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0, 1024);  //获取共享内存缓冲区
        FileLock flock=null;

        for(int i=65;i<91;i++){
            //阻塞独占锁,当文件锁不可用时,当前进程会被挂起
            flock=fc.lock();
            System.out.println(System.currentTimeMillis() +  ":write:" + (char)i);
            mbb.put(i-65,(byte)i);  //从文件第一个字节位置开始写入数据
            flock.release();        //释放锁
            Thread.sleep(1000);
        }
    }
}

2.2、读进程(加锁)

public class NIOReadLock {
    private static RandomAccessFile raf;

    public static void main(String[] args) throws Exception {

        raf = new RandomAccessFile("D:/tmp/data.dat", "rw");
        FileChannel fc = raf.getChannel();
        MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0, 1024);
        FileLock flock=null;

        for(int i=0;i<26;i++){
            flock=fc.lock();    //上锁
            System.out.println( System.currentTimeMillis() +  ":read:" + (char)mbb.get(i));
            flock.release();    //释放锁
            Thread.sleep(1000);
        }
    }
}

2.3、运行读写进程

因为我们采用了文件锁方式来规范读写操作,该方法在读操作和写操作之前都采用加锁来保证数据安全。
【Java】进程通信(共享内存)_第1张图片
【Java】进程通信(共享内存)_第2张图片

2.4、写进程(不加锁)

public class NIOWrite {

    private static RandomAccessFile raf;
    public static void main(String[] args) throws Exception {
        //建立文件和内存的映射,即时双向同步
        raf = new RandomAccessFile("D:/tmp/data.dat", "rw");
        FileChannel fc = raf.getChannel();
        MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0, 1024);

        //清除文件内容 ,对 MappedByteBuffer 的操作就是对文件的操作
        for(int i=0;i<1024;i++){
            mbb.put(i,(byte)0);
        }

        //从文件的第二个字节开始,依次写入 A-Z 字母,第一个字节指明当前操作的位置
        for(int i=65;i<91;i++){
            int index = i-63;
            int flag = mbb.get(0);  //可读标置第一个字节为 0
            if(flag != 0){          //不是可写标示 0,则重复循环,等待
                i--;
                continue;
            }
            mbb.put(0,(byte)1);         //正在写数据,标志第一个字节为 1
            mbb.put(1,(byte)(index));   //文件第二个字节说明,写数据的位置

            System.out.println(System.currentTimeMillis() +  ":position:" + index +"write:" + (char)i);

            mbb.put(index,(byte)i);     //index 位置写入数据
            mbb.put(0,(byte)2);         //置可读数据标志第一个字节为 2

            Thread.sleep(3000);
        }
    }
}

2.4、读进程(不加锁)

public class NIORead {
    private static RandomAccessFile raf;

    public static void main(String[] args) throws Exception {

        raf = new RandomAccessFile("D:/tmp/data.dat", "rw");
        FileChannel fc = raf.getChannel();
        MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0, 1024);
        int lastIndex = 0;

        for(int i=1;i<27;i++){
            int flag = mbb.get(0);      //取读写数据的标志    
            int index = mbb.get(1);     //读取数据的位置,2为可读    

            if(flag != 2 || index == lastIndex){ //假如不可读,或未写入新数据时重复循环    
                i--;
                continue;
            }

            lastIndex = index;
            System.out.println( System.currentTimeMillis() +  ":position:" + index +"read:" + (char)mbb.get(index));

            mbb.put(0,(byte)0);     //置第一个字节为可读标志为 0    

            if(index == 27){        //读完数据后退出    
                break;
            }
        }
    }
}  

你可能感兴趣的:(java,jvm,开发语言)