共享内存对应应用开发的意义
对熟知UNIX系统应用开发的程序员来说IPC(InterProcess Communication)机制是非常熟悉的IPC基本包括共享内存信号灯操作消息队列信号处理等部分是开发应用中非常重要的必不可少的工具其中共享内存IPC机制的关键对于数据共享系统快速查询动态配置减少资源耗费等均有独到的优点
对应UNIX系统来说共享内存分为一般共享内存和映像文件共享内存两种而对应 Windows实际上只有映像文件共享内存一种所以java应用中也是只能创建映像文件共享内存
在java语言中基本上没有提及共享内存这个概念但是在某一些应用中共享内存确实非常有用例如采用java语言的分布式应用系统中存在着大量的分布式共享对象很多时候需要查询这些对象的状态以查看系统是否运行正常或者了解这些对象的目前的一些统计数据和状态如果采用网络通信的方式显然会增加应用的额外负担也增加了一些不必要的应用编程而如果采用共享内存的方式则可以直接通过共享内存查看对象的状态数据和统计数据从而减少了一些不必要的麻烦
共享内存的使用有如下几个特点
可以被多个进程打开访问
读写操作的进程在执行读写操作时其他进程不能进行写操作
多个进程可以交替对某一共享内存执行写操作
一个进程执行了内存的写操作后不影响其他进程对该内存的访问同时其他进程对更新后的内存具有可见性
在进程执行写操作时如果异常退出对其他进程写操作禁止应自动解除
相对共享文件数据访问的方便性和效率有
另外共享内存的使用上有如下情况
独占的写操作相应有独占的写操作等待队列独占的写操作本身不会发生数据的一致性问题
共享的写操作相应有共享的写操作等待队列共享的写操作则要注意防止发生数据的一致性问题
独占的读操作相应有共享的读操作等待队列
共享的读操作相应有共享的读操作等待队列
一般情况下我们只是关心第一二种情况
共享内存在java中的实现
在jdk中提供的类MappedByteBuffer为我们实现共享内存提供了较好的方法该缓沖区实际上是一个磁盘文件的内存映像二者的变化将保持同步即内存数据发生变化会立刻反映到磁盘文件中这样会有效的保证共享内存的实现
将共享内存和磁盘文件建立联系的是文件通道类FileChannel该类的加入是JDK为了统一对外部设备(文件网络接口等)的访问方法并且加强了多线程对同一文件进行存取的安全性例如读写操作统一成read和write这里只是用它来建立共享内存用它建立了共享内存和磁盘文件之间的一个通道
打开一个文件建立一个文件通道可以用RandomAccessFile类中的方法getChannel该方法将直接返回一个文件通道该文件通道由于对应的文件设为随机存取文件一方面可以进行读写两种操作另一方面使用它不会破坏映像文件的内容(如果用FileOutputStream直接打开一个映像文件会将该文件的大小置为当然数据会全部丢失)这里如果用 FileOutputStream和FileInputStream则不能理想的实现共享内存的要求因为这两个类同时实现自由的读写操作要困难得多
下面的代码实现了如上功能它的作用类似UNIX系统中的mmap函数
// 获得一个只读的随机存取文件对象
RandomAccessFile RAFile = new RandomAccessFile(filenamer);
// 获得相应的文件通道
FileChannel fc = RAFilegetChannel();
// 取得文件的实际大小以便映像到共享内存
int size = (int)fcsize();
// 获得共享内存缓沖区该共享内存只读
MappedByteBuffer mapBuf = fcmap(FileChannelMAP_ROsize);
// 获得一个可读写的随机存取文件对象
RAFile = new RandomAccessFile(filenamerw);
// 获得相应的文件通道
fc = RAFilegetChannel();
// 取得文件的实际大小以便映像到共享内存
size = (int)fcsize();
// 获得共享内存缓沖区该共享内存可读写
mapBuf = fcmap(FileChannelMAP_RWsize);
// 获取头部消息存取权限
mode = mapBufgetInt();
如果多个应用映像同一文件名的共享内存则意味着这多个应用共享了同一内存数据这些应用对于文件可以具有同等存取权限一个应用对数据的刷新会更新到多个应用中
为了防止多个应用同时对共享内存进行写操作可以在该共享内存的头部信息加入写操作标志该共享内存的头部基本信息至少有
int Length // 共享内存的长度
int mode; // 该共享内存目前的存取模式
共享内存的头部信息是类的私有信息在多个应用可以对同一共享内存执行写操作时开始执行写操作和结束写操作时需调用如下方法
public boolean StartWrite()
{
if(mode == ) { // 标志为则表示可写
mode = ; // 置标志为意味着别的应用不可写该共享内存
mapBufflip();
mapBufputInt(mode); // 写如共享内存的头部信息
return true;
}
else {
return false; // 指明已经有应用在写该共享内存本应用不可写该共享内存
}
}
public boolean StopWrite()
{
mode = ; // 释放写权限
mapBufflip();
mapBufputInt(mode); // 写入共享内存头部信息
return true;
}
这里提供的类文件mmapjava封装了共享内存的基本接口读者可以用该类扩展成自己需要的功能全面的类
如果执行写操作的应用异常中止那么映像文件的共享内存将不再能执行写操作为了在应用异常中止后写操作禁止标志自动消除必须让运行的应用获知退出的应用在多线程应用中可以用同步方法获得这样的效果但是在多进程中同步是不起作用的方法可以采用的多种技巧这里只是描述一可能的实现采用文件锁的方式写共享内存应用在获得对一个共享内存写权限的时候除了判断头部信息的写权限标志外还要判断一个临时的锁文件是否可以得到如果可以得到则即使头部信息的写权限标志为(上述)也可以启动写权限其实这已经表明写权限获得的应用已经异常退出这段代码如下
// 打开一个临时的文件注意同一共享内存该文件名要相同可以在共享文件名后加后缀lock
RandomAccessFile fis = new RandomAccessFile(shmlockrw);
// 获得文件通道
FileChannel lockfc = fisgetChannel();
// 获得文件的独占锁该方法不产生堵塞立刻返回
FileLock flock = lockfctryLock();
// 如果为空则表明已经有应用占有该锁
if(flock == null) {
// 不能执行写操作
}
else {
// 可以执行写操作
}
该锁会在应用异常退出后自动释放这正是该处所需要的方法
共享内存在java中的应用
共享内存在java应用中经常有如下两种种应用
永久对象配置
在java服务器应用中用户可能会在运行过程中配置一些参数而这些参数需要永久有效当服务器应用重新启动后这些配置参数仍然可以对应用起作用这就可以用到该文中的共享内存该共享内存中保存了服务器的运行参数和一些对象运行特性可以在应用启动时读入以启用以前配置的参数
查询共享数据
一个应用(例 sysjava)是系统的服务进程其系统的运行状态记录在共享内存中其中运行状态可能是不断变化的为了随时了解系统的运行状态启动另一个应用(例 monjava)该应用查询该共享内存汇报系统的运行状态
可见共享内存在java应用中还是很有用的只要组织好共享内存的数据结构共享内存就可以在应用开发中发挥很不错的作用