共享內(nèi)存可以說是最有用的進(jìn)程間通信方式,也是最快的
IPC(
Inter-Process Communication)形式。兩個(gè)不同進(jìn)程A、B共享內(nèi)存的意思是,同一塊物理內(nèi)存被映射到進(jìn)程A、B各自的進(jìn)程地址
空間。進(jìn)程A可以即時(shí)看到進(jìn)程B對(duì)共享內(nèi)存中數(shù)據(jù)的更新,反之亦然。由于多個(gè)進(jìn)程共享同一塊內(nèi)存區(qū)域,必然需要某種同步機(jī)制,互斥鎖和信號(hào)量都可以。
采用
共享內(nèi)存通信的一個(gè)顯而易見的好處是效率高,因?yàn)檫M(jìn)程可以直接讀寫內(nèi)存,而不需要任何數(shù)據(jù)的拷貝。對(duì)于像管道和消息隊(duì)列等通信方式,則需要在內(nèi)核和用
戶空間進(jìn)行四次的數(shù)據(jù)拷貝,而共享內(nèi)存則只拷貝兩次數(shù)據(jù):一次從輸入文件到共享內(nèi)存區(qū),另一次從共享內(nèi)存區(qū)到輸出文件。實(shí)際上,進(jìn)程之間在共享內(nèi)存時(shí),并
不總是讀寫少量數(shù)據(jù)后就解除映射,有新的通信時(shí),再重新建立共享內(nèi)存區(qū)域。而是保持共享區(qū)域,直到通信完畢為止,這樣,數(shù)據(jù)內(nèi)容一直保存在共享內(nèi)存中,并
沒有寫回文件。共享內(nèi)存中的內(nèi)容往往是在解除映射時(shí)才寫回文件的。因此,采用共享內(nèi)存的通信方式效率是非常高的。
共享內(nèi)存的使用有如下幾個(gè)特點(diǎn):
- 可以被多個(gè)進(jìn)程打開訪問;
- 讀寫操作的進(jìn)程在執(zhí)行讀寫操作時(shí)其他進(jìn)程不能進(jìn)行寫操作;
- 多個(gè)進(jìn)程可以交替對(duì)某一共享內(nèi)存執(zhí)行寫操作;
- 一個(gè)進(jìn)程執(zhí)行了內(nèi)存的寫操作后,不影響其他進(jìn)程對(duì)該內(nèi)存的訪問。同時(shí)其他進(jìn)程對(duì)更新后的內(nèi)存具有可見性。
- 在進(jìn)程執(zhí)行寫操作時(shí)如果異常退出,對(duì)其他進(jìn)程寫操作禁止應(yīng)自動(dòng)解除。
- 相對(duì)共享文件,數(shù)據(jù)訪問的更方便更效率
另外,共享內(nèi)存的使用上有如下情況:
獨(dú)占的寫操作,相應(yīng)有獨(dú)占的寫操作等待隊(duì)列。獨(dú)占的寫操作本身不會(huì)發(fā)生數(shù)據(jù)的一致性問題。
共享的寫操作,相應(yīng)有共享的寫操作等待隊(duì)列。共享的寫操作則要注意防止發(fā)生數(shù)據(jù)的一致性問題。
獨(dú)占的讀操作,相應(yīng)有共享的讀操作等待隊(duì)列;
共享的讀操作,相應(yīng)有共享的讀操作等待隊(duì)列。
共享內(nèi)存在java中的實(shí)現(xiàn)
在jdk1.4中提供的類MappedByteBuffer為我們實(shí)現(xiàn)共享內(nèi)存提供了較好的方法。該緩 沖區(qū)實(shí)際上是一個(gè)磁盤文件的內(nèi)存映像。二者的變化將保持同步,即內(nèi)存數(shù)據(jù)發(fā)生變化會(huì)立 刻反映到磁盤文件中,這樣會(huì)有效的保證共享內(nèi)存的實(shí)現(xiàn)。
將
共享內(nèi)存和磁盤文件建立聯(lián)系的是文件通道類:FileChannel。該類的加入是JDK為
了統(tǒng)一對(duì)外部設(shè)備(文件、網(wǎng)絡(luò)接口等)的訪問方法,并且加強(qiáng)了多線程對(duì)同一文件進(jìn)行存
取的安全性。例如讀寫操作統(tǒng)一成read和write。這里只是用它來建立共享內(nèi)存用,它建立 了共享內(nèi)存和磁盤文件之間的一個(gè)通道。
打
開一個(gè)文件建立一個(gè)文件通道可以用RandomAccessFile類中的方法getChannel。該
方法將直接返回一個(gè)文件通道。該文件通道由于對(duì)應(yīng)的文件設(shè)為隨機(jī)存取文件,一方面可以
進(jìn)行讀寫兩種操作,另一方面使用它不會(huì)破壞映像文件的內(nèi)容(如果用FileOutputStream直
接打開一個(gè)映像文件會(huì)將該文件的大小置為0,當(dāng)然數(shù)據(jù)會(huì)全部丟失)。這里,如果用
FileOutputStream和FileInputStream則不能理想的實(shí)現(xiàn)共享內(nèi)存的要求,因?yàn)檫@兩個(gè)類同時(shí) 實(shí)現(xiàn)自由的讀寫操作要困難得多。
下面的代碼實(shí)現(xiàn)了如上功能,它的作用類似UNIX系統(tǒng)中的mmap函數(shù)。
// 獲得一個(gè)只讀的隨機(jī)存取文件對(duì)象
RandomAccessFile RAFile = new RandomAccessFile(filename,"r");
// 獲得相應(yīng)的文件通道
FileChannel fc = RAFile.getChannel();
// 取得文件的實(shí)際大小,以便映像到共享內(nèi)存
int size = (int)fc.size();
// 獲得共享內(nèi)存緩沖區(qū),該共享內(nèi)存只讀
MappedByteBuffer mapBuf = fc.map(FileChannel.MAP_RO,0,size);
// 獲得一個(gè)可讀寫的隨機(jī)存取文件對(duì)象
RAFile = new RandomAccessFile(filename,"rw");
// 獲得相應(yīng)的文件通道
fc = RAFile.getChannel();
// 取得文件的實(shí)際大小,以便映像到共享內(nèi)存
size = (int)fc.size();
// 獲得共享內(nèi)存緩沖區(qū),該共享內(nèi)存可讀寫
mapBuf = fc.map(FileChannel.MAP_RW,0,size);
// 獲取頭部消息:存取權(quán)限
mode = mapBuf.getInt();
如果多個(gè)應(yīng)用映像同一文件名的共享內(nèi)存,則意味著這多個(gè)應(yīng)用共享了同一內(nèi)存數(shù)據(jù)。 這些應(yīng)用對(duì)于文件可以具有同等存取權(quán)限,一個(gè)應(yīng)用對(duì)數(shù)據(jù)的刷新會(huì)更新到多個(gè)應(yīng)用中。
為了防止多個(gè)應(yīng)用同時(shí)對(duì)共享內(nèi)存進(jìn)行寫操作,可以在該共享內(nèi)存的頭部信息加入寫操 作標(biāo)志。該共享內(nèi)存的頭部基本信息至少有:
int Length; // 共享內(nèi)存的長度。
int mode; // 該共享內(nèi)存目前的存取模式。
共享內(nèi)存的頭部信息是類的私有信息,在多個(gè)應(yīng)用可以對(duì)同一共享內(nèi)存執(zhí)行寫操作時(shí), 開始執(zhí)行寫操作和結(jié)束寫操作時(shí),需調(diào)用如下方法:
public boolean StartWrite()
{
if(mode == 0) { // 標(biāo)志為0,則表示可寫
mode = 1; // 置標(biāo)志為1,意味著別的應(yīng)用不可寫該共享內(nèi)存
mapBuf.flip();
mapBuf.putInt(mode); // 寫如共享內(nèi)存的頭部信息
return true;
}
else {
return false; // 指明已經(jīng)有應(yīng)用在寫該共享內(nèi)存,本應(yīng)用不可寫該共享內(nèi)存
}
}
public boolean StopWrite()
{
mode = 0; // 釋放寫權(quán)限
mapBuf.flip();
mapBuf.putInt(mode); // 寫入共享內(nèi)存頭部信息
return true;
}
這里提供的類文件mmap.java封裝了共享內(nèi)存的基本接口,讀者可以用該類擴(kuò)展成自己 需要的功能全面的類。
如
果執(zhí)行寫操作的應(yīng)用異常中止,那么映像文件的共享內(nèi)存將不再能執(zhí)行寫操作。為了
在應(yīng)用異常中止后,寫操作禁止標(biāo)志自動(dòng)消除,必須讓運(yùn)行的應(yīng)用獲知退出的應(yīng)用。在多線
程應(yīng)用中,可以用同步方法獲得這樣的效果,但是在多進(jìn)程中,同步是不起作用的。方法可
以采用的多種技巧,這里只是描述一可能的實(shí)現(xiàn):采用文件鎖的方式。寫共享內(nèi)存應(yīng)用在獲
得對(duì)一個(gè)共享內(nèi)存寫權(quán)限的時(shí)候,除了判斷頭部信息的寫權(quán)限標(biāo)志外,還要判斷一個(gè)臨時(shí)的
鎖文件是否可以得到,如果可以得到,則即使頭部信息的寫權(quán)限標(biāo)志為1(上述),也可以
啟動(dòng)寫權(quán)限,其實(shí)這已經(jīng)表明寫權(quán)限獲得的應(yīng)用已經(jīng)異常退出,這段代碼如下:
// 打開一個(gè)臨時(shí)的文件,注意同一共享內(nèi)存,該文件名要相同,可以在共享文件名后加后綴“.lock”。
RandomAccessFile fis = new RandomAccessFile("shm.lock","rw");
// 獲得文件通道
FileChannel lockfc = fis.getChannel();
// 獲得文件的獨(dú)占鎖,該方法不產(chǎn)生堵塞,立刻返回
FileLock flock = lockfc.tryLock();
// 如果為空,則表明已經(jīng)有應(yīng)用占有該鎖
if(flock == null) {
...// 不能執(zhí)行寫操作
}
else {
...// 可以執(zhí)行寫操作
}
該鎖會(huì)在應(yīng)用異常退出后自動(dòng)釋放,這正是該處所需要的方法。
3 共享內(nèi)存在java中的應(yīng)用
共享內(nèi)存在java應(yīng)用中,經(jīng)常有如下兩種種應(yīng)用:
永久對(duì)象配置。
在
java服務(wù)器應(yīng)用中,用戶可能會(huì)在運(yùn)行過程中配置一些參數(shù),而這些參數(shù)需要永久
有效,當(dāng)服務(wù)器應(yīng)用重新啟動(dòng)后,這些配置參數(shù)仍然可以對(duì)應(yīng)用起作用。這就可以用到該文
中的共享內(nèi)存。該共享內(nèi)存中保存了服務(wù)器的運(yùn)行參數(shù)和一些對(duì)象運(yùn)行特性??梢栽趹?yīng)用啟 動(dòng)時(shí)讀入以啟用以前配置的參數(shù)。
查詢共享數(shù)據(jù)。
一個(gè)應(yīng)用是系統(tǒng)的服務(wù)進(jìn)程,其系統(tǒng)的運(yùn)行狀態(tài)記錄在共享內(nèi)存中,其中運(yùn)行狀態(tài)可能是不斷變化的。為了隨時(shí)了解系統(tǒng)的運(yùn)行狀態(tài),啟動(dòng)另一個(gè)應(yīng)用,該應(yīng)用查詢?cè)摴蚕韮?nèi)存,匯報(bào)系統(tǒng)的運(yùn)行狀態(tài)。
可見,共享內(nèi)存在java應(yīng)用中還是很有用的,只要組織好共享內(nèi)存的數(shù)據(jù)結(jié)構(gòu),共享內(nèi)存就可以在應(yīng)用開發(fā)中發(fā)揮很不錯(cuò)的作用。