Java进程间通信学习
转自:https://www.iteye.com/blog/polim-1278435
进程间通信的主要方法有:
(1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
(2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
(3)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。Linux中可以使用kill -12 进程号,像当前进程发送信号,但前提是发送信号的进程要注册该信号。
example:
OperateSignal operateSignalHandler = new OperateSignal();
Signal sig = new Signal("USR2");
Signal.handle(sig, operateSignalHandler);
(4)消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺限。
(5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
(6)内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
Java 中有类 MappedByteBuffer实现内存映射
(7)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
(8)套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
管道方式
一、Java 启动子进程方式
1、
Runtime rt = Runtime.getRuntime();
Process process = rt.exec("java com.test.process.T3");
2、
ProcessBuilder pb = new ProcessBuilder("java", "com.test.process.T3");
Process p = pb.start();
二、Java父、子进程通信方式(管道方式)
父进程获取子进程输出流方式
BufferedInputStream in = new BufferedInputStream(p.getInputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String s;
while ((s = br.readLine()) != null) {
System.out.println(s);
}
子进程获取父进程输入流方式
package com.test.process;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader; public class T3 { public static void main(String[] args) throws IOException {
System.out.println("子进程被调用成功!"); BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in)); while (true) {
String strLine = bfr.readLine();
if (strLine != null) {
System.out.println("hi:" + strLine);
}
}
} }
三、详细测试类
父进程测试类:
package com.test.process.pipe;
import java.io.IOException; public class ProcessTest { public static void main(String[] args) throws IOException, InterruptedException {
Process p = Runtime.getRuntime().exec("java com.test.process.pipe.MyTest"); StringBuilder sbuilder = new StringBuilder();
for(int k=0;k<1;k++){
sbuilder.append("hello");
} int outSize = 1;
TestOut out[] = new TestOut[outSize];
for(int i=0;i<outSize;i++){
out[i] = new TestOut(p,sbuilder.toString().getBytes());
new Thread(out[i]).start();
} int inSize = 1;
TestIn in[] = new TestIn[inSize];
for(int j=0;j<inSize;j++){
in[j] = new TestIn(p);
new Thread(in[j]).start();
}
}
}
子进程类
package com.test.process.pipe;
import java.io.BufferedReader;
import java.io.InputStreamReader; public class MyTest {
public static void main(String[] args) throws Exception {
//读取父进程输入流
BufferedReader bfr = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String strLine = bfr.readLine();
if (strLine != null) {
System.out.println(strLine);//这个地方的输出在子进程控制台是无法输出的,只可以在父进程获取子进程的输出
}else {
return;
}
}
}
}
TestIn类
package com.test.process.pipe;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader; public class TestIn implements Runnable{ private Process p = null;
public TestIn(Process process){
p = process;
} @Override
public void run() {
try {
InputStream in = p.getInputStream();
BufferedReader bfr = new BufferedReader(new InputStreamReader(in));
String rd = bfr.readLine();
if(rd != null){
System.out.println(rd);//输出子进程返回信息(即子进程中的System.out.println()内容)
}else{
return;
}
//注意这个地方,如果关闭流则子进程的返回信息无法获取,如果不关闭只有当子进程返回字节为8192时才返回,为什么是8192下面说明.
//bfr.close();
//in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
TestOut类
package com.test.process.pipe;
import java.io.IOException;
import java.io.OutputStream; public class TestOut implements Runnable { private Process p = null;
private byte []b = null; public TestOut(Process process,byte byt[]){
p = process;
b = byt;
} @Override
public void run() {
try {
OutputStream ops = p.getOutputStream();
//System.out.println("out--"+b.length);
ops.write(b);
//注意这个地方如果关闭,则父进程只可以给子进程发送一次信息,如果这个地方开启close()则父进程给子进程不管发送大小多大的数据,子进程都可以返回
//如果这个地方close()不开启,则父进程给子进程发送数据累加到8192子进程才返回。
//ops.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
备注:
1、子进程的输出内容是无法在控制台输出的,只能再父类中获取并输出。
2、父进程往子进程写内容时如果关闭字节流,则子进程的输入流同时关闭。
3、如果父进程中输入、输出流都不关闭,子进程获取的字节流在达到8129byte时才返回。
4、关闭子进程一定要在父进程中关闭 p.destroy()
实例1:
/**
*如下另一种情况说明
*如果像如下情况执行会出现说明情况呢
*前提说明:TestOut类中开启ops.close();
*/
package com.test.process.pipe;
import java.io.IOException; public class ProcessTest { public static void main(String[] args) throws IOException, InterruptedException {
Process p = Runtime.getRuntime().exec("java com.test.process.pipe.MyTest"); TestOut out = new TestOut(p,"Hello everyone".getBytes());
new Thread(out).start(); TestIn ti = new TestIn(p);
new Thread(ti).start(); Thread.sleep(3000); TestOut out2 = new TestOut(p,"-Hello-everyone".getBytes());
new Thread(out2).start(); TestIn ti2 = new TestIn(p);
new Thread(ti2).start();
}
}
执行后输出结果为:
Hello everyone
java.io.IOException: Stream closed
at java.io.BufferedInputStream.getBufIfOpen(BufferedInputStream.java:145
)
at java.io.BufferedInputStream.read(BufferedInputStream.java:308)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
at java.io.InputStreamReader.read(InputStreamReader.java:167)
at java.io.BufferedReader.fill(BufferedReader.java:136)
at java.io.BufferedReader.readLine(BufferedReader.java:299)
at java.io.BufferedReader.readLine(BufferedReader.java:362)
at com.test.process.pipe.TestIn.run(TestIn.java:20)
at java.lang.Thread.run(Thread.java:662)
由此可见当创建一个子进程后,p.getOutputStream();p.getInputStream();通过两种方式使父进程与子进程建立管道连接,而当close()连接时管道关闭,在通过调用
p.getOutputStream();p.getInputStream();时直接出现IOException,结论为当父子进程建立连接后,通过管道长连接的方式进程信息传输,当close时在通过获取子进程的输入输出流
都会出现IOException
实例2:
在实例1的基础上进行修改,会出现什么结果呢,如下
package com.test.process.pipe;
import java.io.IOException; public class ProcessTest { public static void main(String[] args) throws IOException, InterruptedException {
Process p = Runtime.getRuntime().exec("java com.test.process.pipe.MyTest"); TestOut out = new TestOut(p,"Hello everyone".getBytes());
new Thread(out).start(); TestIn ti = new TestIn(p);
new Thread(ti).start(); Process p2 = Runtime.getRuntime().exec("java com.test.process.pipe.MyTest");
TestOut out2 = new TestOut(p2,"-Hello-everyone".getBytes());
new Thread(out2).start(); TestIn ti2 = new TestIn(p2);
new Thread(ti2).start();
}
}
输出结果:
Hello everyone
-Hello-everyone
综上可见每个父进程创建一个子进程后,通过p.getOutputStream();p.getInputStream();建立管道连接后,无法关闭流,如果关闭了则需要重新建立进程才可以达到通信的效果。
如果不关闭流,则传输的字符内容累加到8192byte时才可以返回。
为什么是8192byte呢?
JDK 源码分析
class TestLambda {
@FunctionalInterface
interface A {
int use();
} public static int getValue(int value) {
return value;
} public void useValue(int value) {
A a = () -> { return getValue(value); };
}
} Process p = Runtime.getRuntime().exec("java com.test.process.pipe.MyTest"); public Process exec(String command) throws IOException {
return exec(command, null, null);
} public Process exec(String command, String[] envp, File dir)
throws IOException {
if (command.length() == 0)
throw new IllegalArgumentException("Empty command"); StringTokenizer st = new StringTokenizer(command);
String[] cmdarray = new String[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++)
cmdarray[i] = st.nextToken();
return exec(cmdarray, envp, dir);
} public Process exec(String[] cmdarray, String[] envp, File dir)
throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
接下来会执行 ProcessBuilder.start
return ProcessImpl.start(cmdarray,environment,dir,redirectErrorStream);
执行ProcessImpl.start(final class ProcessImpl extends Process )
OutputStream
InputStream 是在这里声明的
如下:
//关键这个地方 创建的为FileDescriptor 管理的方式底层也是通过文件的方式实现的,原理跟linux的管道相同
stdin_fd = new FileDescriptor();
stdout_fd = new FileDescriptor();
stderr_fd = new FileDescriptor(); handle = create(cmdstr, envblock, path, redirectErrorStream,
stdin_fd, stdout_fd, stderr_fd); java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
stdin_stream =
new BufferedOutputStream(new FileOutputStream(stdin_fd));
stdout_stream =
new BufferedInputStream(new FileInputStream(stdout_fd));
stderr_stream =
new FileInputStream(stderr_fd);
return null;
}
});
}
Process类中的说明
public abstract class Process
{
/**
* Gets the output stream of the subprocess.
* Output to the stream is piped into the standard input stream of
* the process represented by this <code>Process</code> object.
* <p> //该处说明OutputStream 是通过管道的方式进行的处理
* Implementation note: It is a good idea for the output stream to
* be buffered.
*
* @return the output stream connected to the normal input of the
* subprocess.
*/
abstract public OutputStream getOutputStream()
}
BufferedReader类中
private static int defaultCharBufferSize = 8192;//默认字符数组长度
另外Java中还提供了PipedInputStream、PipedOutputStream类,但这2个类用在多进程间交互是无法实现的。
总结:
1、如果Java中要涉及到多进程之间交互,子进程只是简单的做一些功能处理的话建议使用
Process p = Runtime.getRuntime().exec("java ****类名");
p.getOutputStream()
p.getInputStream() 的方式进行输入、输出流的方式进行通信
如果涉及到大量的数据需要在父子进程之间交互不建议使用该方式,该方式子类中所有的System都会返回到父类中,另该方式不太适合大并发多线程
2、内存共享(MappedByteBuffer)
该方法可以使用父子进程之间通信,但在高并发往内存内写数据、读数据时需要对文件内存进行锁机制,不然会出现读写内容混乱和不一致性,Java里面提供了文件锁FileLock,但这个在父/子进程中锁定后另一进程会一直等待,效率确实不够高。
RandomAccessFile raf = new RandomAccessFile("D:/a.txt", "rw");
FileChannel fc = raf.getChannel();
MappedByteBuffer mbb = fc.map(MapMode.READ_WRITE, 0, 1024);
FileLock fl = fc.lock();//文件锁
3、Socket 这个方式可以实现,需要在父子进程间进行socket通信
4、队列机制 这种方式也可以实现,需要父/子进程往队列里面写数据,子/父进程进行读取。不太好的地方是需要在父子进程之间加一层队列实现,队列实现有ActiveMQ,FQueue等
5、通过JNI方式,父/子进程通过JNI对共享进程读写
6、基于信号方式,但该方式只能在执行kill -12或者在cmd下执行ctrl+c 才会触发信息发生。
OperateSignal operateSignalHandler = new 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中没有命名管道
参考:
Java进程间通信学习的更多相关文章
- Java的学习之路
记事本 EditPlus eclipse Java的学习软件,已经系统性学习Java有一段时间了,接下来我想讲一下我在Java学习用到的软件. 1.第一个软件:记事本 记事本是Java学习中最基础的编 ...
- Java多线程学习笔记
进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间.(只负责空间分配) 线程:进程中的一个执行单元,负责进程汇总的程序的运行,一个进程当中至少要有一个线程. 多线程:一个进程中时可以有多个线 ...
- Java Web 学习路线
实际上,如果时间安排合理的话,大概需要六个月左右,有些基础好,自学能力强的朋友,甚至在四个月左右就开始找工作了.大三的时候,我萌生了放弃本专业的念头,断断续续学 Java Web 累计一年半左右,总算 ...
- Java基础学习-- 继承 的简单总结
代码参考:Java基础学习小记--多态 为什么要引入继承? 还是做一个媒体库,里面可以放CD,可以放DVD.如果把CD和DVD做成两个没有联系的类的话,那么在管理这个媒体库的时候,要单独做一个添加CD ...
- 20145213《Java程序设计学习笔记》第六周学习总结
20145213<Java程序设计学习笔记>第六周学习总结 说在前面的话 上篇博客中娄老师指出我因为数据结构基础薄弱,才导致对第九章内容浅尝遏止地认知.在这里我还要自我批评一下,其实我事后 ...
- [原创]java WEB学习笔记95:Hibernate 目录
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- Java多线程学习(转载)
Java多线程学习(转载) 时间:2015-03-14 13:53:14 阅读:137413 评论:4 收藏:3 [点我收藏+] 转载 :http://blog ...
- java基础学习总结——java环境变量配置
前言 学习java的第一步就要搭建java的学习环境,首先是要安装JDK,JDK安装好之后,还需要在电脑上配置"JAVA_HOME”."path”."classpath& ...
- Java Web学习系列——Maven Web项目中集成使用Spring、MyBatis实现对MySQL的数据访问
本篇内容还是建立在上一篇Java Web学习系列——Maven Web项目中集成使用Spring基础之上,对之前的Maven Web项目进行升级改造,实现对MySQL的数据访问. 添加依赖Jar包 这 ...
随机推荐
- 唐敬博-201871010118 《面向对象程序设计(java)》第六、七周学习总结
在博客园撰写博客(随笔),总结6-7周学习内容(包括实验内容),作业格式要求如下: 博文名称:学号-姓名<面向对象程序设计(java)>第四周学习总结(1分) 博文正文开头格式:(2分) ...
- Autofac注册组件详解
注册概念:我们通过创建 ContainerBuilder 来注册 组件 并且告诉容器哪些 组件 暴露了哪些 服务.组件 可以通过 反射 创建; 通过提供现成的 实例创建; 或者通过 lambda 表达 ...
- 使用dva 的思考的一个问题,数组复制的必要
*getTags({ payload }, { call, put }) { const response = yield call(getTags, payload); const arr = re ...
- ESA2GJK1DH1K升级篇: STM32远程乒乓升级,基于(Wi-Fi模块AT指令TCP透传方式),MQTT通信控制升级
实现功能概要 前面的版本都是,定时访问云端的程序版本,如果版本不一致,然后下载最新的升级文件,实现升级. 这一节,在用户程序里面加入MQTT通信,执行用户程序的时候,通过接收MQTT的升级命令实现升级 ...
- 【转载】预处器的对比——Sass、LESS和Stylus
常用的3大css预编译器:Sass.LESS和Stylus,你是否会混淆它们的区别和用法.这里有篇文章介绍的挺详细. 传送门:https://www.w3cplus.com/css/sass-vs-l ...
- [LeetCode] 660. Remove 9 移除9
Start from integer 1, remove any integer that contains 9 such as 9, 19, 29... So now, you will have ...
- [LeetCode] 305. Number of Islands II 岛屿的数量之二
A 2d grid map of m rows and n columns is initially filled with water. We may perform an addLand oper ...
- vs2017远程调试
使用情景:服务器端程序出了问题,但本地开发环境又无法模拟.此时需要使用VS进行远程调试. 使用方法: 找到VS安装路径,将Remote Debuuger拷贝到服务器端 (注意一定要和你本地运行的VS版 ...
- Intellij-Cannot download Sources解决方法
当你点击Dowload Sources的时候它会报一个错误 提示你不能下载源代码,这个时候你可以打开下方的命令窗口 然后 进入到项目根路径后 使用mvn dependency:resolve -Dc ...
- 通过欧拉计划学习Rust编程(第22~25题)
最近想学习Libra数字货币的MOVE语言,发现它是用Rust编写的,所以先补一下Rust的基础知识.学习了一段时间,发现Rust的学习曲线非常陡峭,不过仍有快速入门的办法. 学习任何一项技能最怕没有 ...