java多进程

进程间的通讯无非就是读写文件,socket通讯或者使用共享内存。

你不想用读写文件的方式,那就用共享内存或者socket通讯的方式。我个人觉得用socket比较简单,也许是因为我对socket比较熟悉。

下面是一篇java实现共享内存的文章,java没法管理内存,其实他也是靠创建映像文件来实现的。

 

共享内存在java中的实现
在jdk1.4中提供的类MappedByteBuffer为我们实现共享内存提供了较好的方法。该缓冲区实际上是一个磁盘文件的内存映像。二者的变化将保持同步,即内存数据发生变化会立刻反映到磁盘文件中,这样会有效的保证共享内存的实现。


将共享内存和磁盘文件建立联系的是文件通道类:FileChannel。该类的加入是JDK为了统一对外部设备(文件、网络接口等)的访问方法,并且加强了多线程对同一文件进行存取的安全性。例如读写操作统一成read和write。这里只是用它来建立共享内存用,它建立了共享内存和磁盘文件之间的一个通道。


打开一个文件建立一个文件通道可以用RandomAccessFile类中的方法getChannel。该方法将直接返回一个文件通道。该文件通道由于对应的文件设为随机存取文件,一方面可以进行读写两种操作,另一方面使用它不会破坏映像文件的内容(如果用FileOutputStream直接打开一个映像文件会将该文件的大小置为0,当然数据会全部丢失)。这里,如果用 FileOutputStream和FileInputStream则不能理想的实现共享内存的要求,因为这两个类同时实现自由的读写操作要困难得多。


下面的代码实现了如上功能,它的作用类似UNIX系统中的mmap函数。
// 获得一个只读的随机存取文件对象
RandomAccessFile RAFile =>FileChannel fc = RAFile.getChannel();
// 取得文件的实际大小,以便映像到共享内存
int size = (int)fc.size();
// 获得共享内存缓冲区,该共享内存只读
MappedByteBuffer mapBuf = fc.map(FileChannel.MAP_RO,0,size);
// 获得一个可读写的随机存取文件对象
RAFile = new RandomAccessFile(filename,"rw");
// 获得相应的文件通道
fc = RAFile.getChannel();
// 取得文件的实际大小,以便映像到共享内存
size = (int)fc.size();
// 获得共享内存缓冲区,该共享内存可读写
mapBuf = fc.map(FileChannel.MAP_RW,0,size);
// 获取头部消息:存取权限
mode = mapBuf.getInt();


如果多个应用映像同一文件名的共享内存,则意味着这多个应用共享了同一内存数据。这些应用对于文件可以具有同等存取权限,一个应用对数据的刷新会更新到多个应用中。
为了防止多个应用同时对共享内存进行写操作,可以在该共享内存的头部信息加入写操作标志。该共享内存的头部基本信息至少有:
int Length; // 共享内存的长度。
int mode; // 该共享内存目前的存取模式。

 

共享内存的头部信息是类的私有信息,在多个应用可以对同一共享内存执行写操作时,开始执行写操作和结束写操作时,需调用如下方法:
public boolean StartWrite()
{
   if(mode == 0) { // 标志为0,则表示可写
     mode = 1; // 置标志为1,意味着别的应用不可写该共享内存
     mapBuf.flip();
     mapBuf.putInt(mode); // 写如共享内存的头部信息
     return true;
  }
  else {
    return false; // 指明已经有应用在写该共享内存,本应用不可写该共享内存
  }
}
public boolean StopWrite()
{
  mode = 0; // 释放写权限
  mapBuf.flip();
  mapBuf.putInt(mode); // 写入共享内存头部信息
  return true;

}

这里提供的类文件mmap.java封装了共享内存的基本接口,读者可以用该类扩展成自己需要的功能全面的类。

 

如果执行写操作的应用异常中止,那么映像文件的共享内存将不再能执行写操作。为了在应用异常中止后,写操作禁止标志自动消除,必须让运行的应用获知退出的应用。在多线程应用中,可以用同步方法获得这样的效果,但是在多进程中,同步是不起作用的。方法可以采用的多种技巧,这里只是描述一可能的实现:采用文件锁的方式。写共享内存应用在获得对一个共享内存写权限的时候,除了判断头部信息的写权限标志外,还要判断一个临时的锁文件是否可以得到,如果可以得到,则即使头部信息的写权限标志为1(上述),也可以启动写权限,其实这已经表明写权限获得的应用已经异常退出,这段代码如下:
// 打开一个临时的文件,注意同一共享内存,该文件名要相同,可以在共享文件名后加后缀“.lock”。
RandomAccessFile fis = new RandomAccessFile("shm.lock","rw");
// 获得文件通道
FileChannel lockfc = fis.getChannel();
// 获得文件的独占锁,该方法不产生堵塞,立刻返回
FileLock flock = lockfc.tryLock();
// 如果为空,则表明已经有应用占有该锁
if(flock == null) {
...// 不能执行写操作
}else {
...// 可以执行写操作
}

 

 

 

进程间通信的主要方法有:
(1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
(2)命名管道(named> Signal sig = new Signal("USR2");
Signal.handle(sig,> Process process = rt.exec("java com.test.process.T3");
2、
ProcessBuilder> Process p = pb.start();

二、Java父、子进程通信方式(管道方式)
父进程获取子进程输出流方式
BufferedInputStream> BufferedReader br = new BufferedReader(new InputStreamReader(in));
String>   System.out.println(s);
}

子进程获取父进程输入流方式
package>
while (true) {
String> if (strLine != null) {
System.out.println("hi:" +>
StringBuilder> for(int k=0;k<1;k++){
sbuilder.append("hello");
}

int> TestOut out[] = new TestOut[outSize];
for(int> out[i] = new TestOut(p,sbuilder.toString().getBytes());
new Thread(out[i]).start();
}

int> TestIn in[] = new TestIn[inSize];
for(int j=0;j<inSize;j++){
in[j] => while (true) {
String> if (strLine != null) {
System.out.println(strLine);//这个地方的输出在子进程控制台是无法输出的,只可以在父进程获取子进程的输出
}else {
return;
}
}
}
}

TestIn类

package> public TestIn(Process process){
p => BufferedReader bfr = new BufferedReader(new InputStreamReader(in));
String> if(rd != null){
System.out.println(rd);//输出子进程返回信息(即子进程中的System.out.println()内容)
}else{
return;
}
//注意这个地方,如果关闭流则子进程的返回信息无法获取,如果不关闭只有当子进程返回字节为8192时才返回,为什么是8192下面说明.
//bfr.close();
//in.close();
}> private byte []b = null;

public TestOut(Process> b = byt;
}

@Override
public> //System.out.println("out--"+b.length);
ops.write(b);
//注意这个地方如果关闭,则父进程只可以给子进程发送一次信息,如果这个地方开启close()则父进程给子进程不管发送大小多大的数据,子进程都可以返回
//如果这个地方close()不开启,则父进程给子进程发送数据累加到8192子进程才返回。
//ops.close();
}>
TestOut> new Thread(out).start();

TestIn> new Thread(ti).start();

Thread.sleep(3000);

TestOut> new Thread(out2).start();

TestIn> new Thread(ti2).start();
}
}


执行后输出结果为:
Hello>
TestOut> new Thread(out).start();

TestIn> new Thread(ti).start();

Process> TestOut out2 = new TestOut(p2,"-Hello-everyone".getBytes());
new Thread(out2).start();

TestIn> new Thread(ti2).start();
}
}


输出结果:
Hello>
public Process>             throw new IllegalArgumentException("Empty command");

StringTokenizer> String[] cmdarray = new String[st.countTokens()];
for (int>     cmdarray[i] = st.nextToken();
return> stdout_fd = new FileDescriptor();
stderr_fd => stdin_fd, stdout_fd, stderr_fd);

java.security.AccessController.doPrivileged(
   >     new BufferedOutputStream(new FileOutputStream(stdin_fd));
stdout_stream =
   >     new FileInputStream(stderr_fd);
return>
另外Java中还提供了PipedInputStream、PipedOutputStream类,但这2个类用在多进程间交互是无法实现的。


总结:
1、如果Java中要涉及到多进程之间交互,子进程只是简单的做一些功能处理的话建议使用
Process> p.getOutputStream()
p.getInputStream() 的方式进行输入、输出流的方式进行通信
如果涉及到大量的数据需要在父子进程之间交互不建议使用该方式,该方式子类中所有的System都会返回到父类中,另该方式不太适合大并发多线程
2、内存共享(MappedByteBuffer)
该方法可以使用父子进程之间通信,但在高并发往内存内写数据、读数据时需要对文件内存进行锁机制,不然会出现读写内容混乱和不一致性,Java里面提供了文件锁FileLock,但这个在父/子进程中锁定后另一进程会一直等待,效率确实不够高。
RandomAccessFile> FileChannel fc = raf.getChannel();  
MappedByteBuffer> FileLock fl = fc.lock();//文件锁
3、Socket 这个方式可以实现,需要在父子进程间进行socket通信
4、队列机制 这种方式也可以实现,需要父/子进程往队列里面写数据,子/父进程进行读取。不太好的地方是需要在父子进程之间加一层队列实现,队列实现有ActiveMQ,FQueue等
5、通过JNI方式,父/子进程通过JNI对共享进程读写
6、基于信号方式,但该方式只能在执行kill -12或者在cmd下执行ctrl+c 才会触发信息发生。
OperateSignal> Signal sig = new Signal("SEGV");//SEGV 这个linux和window不同
Signal.handle(sig, operateSignalHandler);

public class OperateSignal implements SignalHandler{
@Override
public void handle(Signal arg0) {
System.out.println("信号接收");
}
}
7、要是在线程间也可以使用Semaphore
8、说明一下Java中没有命名管道

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

此间的年少

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值