java 之进程

java 之进程

一、Java中关于应用程序和进程相关的概念

在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认为java.exe或者javaw.exe(windows下可以通过任务管理器查看)。Java采用的是单线程编程模型,即在我们自己的程序中如果没有主动创建线程的话,只会创建一个线程,通常称为主线程。但是要注意,虽然只有一个线程来执行任务,不代表JVM中只有一个线程,JVM实例在创建的时候,同时会创建很多其他的线程(比如垃圾收集器线程)。

二、Java 中使用进程的方式

1. 创建进程

在Java中,可以通过两种方式来创建进程,总共涉及到5个主要的类。

第一种方式是通过Runtime.exec()方法来创建一个进程,第二种方法是通过ProcessBuilder的start方法来创建进程。下面就来讲一讲这2种方式的区别和联系。

首先要讲的是Process类,Process类是一个抽象类,在它里面主要有几个抽象的方法,这个可以通过查看Process类的源代码得知:

public abstract class Process
{
     
    abstract public OutputStream getOutputStream();   //获取进程的输出流
       
    abstract public InputStream getInputStream();    //获取进程的输入流
  
    abstract public InputStream getErrorStream();   //获取进程的错误流
  
    abstract public int waitFor() throws InterruptedException;   //让进程等待
   
    abstract public int exitValue();   //获取进程的退出标志
  
    abstract public void destroy();   //摧毁

1.1 方式一: 通过ProcessBuilder创建进程

  ProcessBuilder是一个final类,它有两个构造器:

public final class ProcessBuilder
{
    private List<String> command;
    private File directory;
    private Map<String,String> environment;
    private boolean redirectErrorStream;
  
    public ProcessBuilder(List<String> command) {
    if (command == null)
        throw new NullPointerException();
    this.command = command;
    }
  
    public ProcessBuilder(String... command) {
    this.command = new ArrayList<String>(command.length);
    for (String arg : command)
        this.command.add(arg);
    }
}

  构造器中传递的是需要创建的进程的命令参数,第一个构造器是将命令参数放进List当中传进去,第二构造器是以不定长字符串的形式传进去。

  那么我们接着往下看,前面提到是通过ProcessBuilder的start方法来创建一个新进程的,我们看一下start方法中具体做了哪些事情。下面是start方法的具体实现源代码:

public Process start() throws IOException {
// Must convert to array first -- a malicious user-supplied
// list might try to circumvent the security check.
String[] cmdarray = command.toArray(new String[command.size()]);
for (String arg : cmdarray)
    if (arg == null)
    throw new NullPointerException();
// Throws IndexOutOfBoundsException if command is empty
String prog = cmdarray[0];
  
SecurityManager security = System.getSecurityManager();
if (security != null)
    security.checkExec(prog);
  
String dir = directory == null ? null : directory.toString();
  
try {
    return ProcessImpl.start(cmdarray,
                 environment,
                 dir,
                 redirectErrorStream);
} catch (IOException e) {
    // It's much easier for us to create a high-quality error
    // message than the low-level C code which found the problem.
    throw new IOException(
    "Cannot run program \"" + prog + "\""
    + (dir == null ? "" : " (in directory \"" + dir + "\")")
    + ": " + e.getMessage(),
    e);
}
}

 该方法返回一个Process对象,该方法的前面部分相当于是根据命令参数以及设置的工作目录进行一些参数设定,最重要的是try语句块里面的一句:

return ProcessImpl.start(cmdarray,
                    environment,
                    dir,
                    redirectErrorStream);

 说明真正创建进程的是这一句,注意调用的是ProcessImpl类的start方法,此处可以知道start必然是一个静态方法。那么ProcessImpl又是什么类呢?该类同样位于java.lang.ProcessImpl路径下,看一下该类的具体实现:

  ProcessImpl也是一个final类,它继承了Process类:

final class ProcessImpl extends Process {
  
    // System-dependent portion of ProcessBuilder.start()
    static Process start(String cmdarray[],
             java.util.Map<String,String> environment,
             String dir,
             boolean redirectErrorStream)
    throws IOException
    {
    String envblock = ProcessEnvironment.toEnvironmentBlock(environment);
    return new ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);
    }
 ....
}

  这是ProcessImpl类的start方法的具体实现,而事实上start方法中是通过这句来创建一个ProcessImpl对象的:

return new ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);

  下面看一下具体使用ProcessBuilder创建进程的例子,比如我要通过ProcessBuilder来启动一个进程打开cmd,并获取ip地址信息,那么可以这么写:

    public static void function() {

        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command("/bin/bash", "-c", "ifconfig");
        processBuilder.redirectErrorStream(true);

        try {
            Process process = processBuilder.start();
            //打印输出到控制台,如果要写入文件,还有一种方式
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("process.log"));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                bufferedWriter.write(line);
                bufferedWriter.newLine();
            }
            bufferedWriter.close();
            bufferedReader.close();

            int exitValue = process.exitValue();
            System.out.println("exitValue is " + exitValue);

        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

1.2 通过Runtime的exec方法来创建进程

    public static void function1() {

        Process exec = null;
        try {
            exec = Runtime.getRuntime().exec("ifconfig");

            BufferedReader bufferReader = new BufferedReader(new InputStreamReader(exec.getInputStream()));
            String line;
            while ((line = bufferReader.readLine()) != null) {
                System.out.println(line);
            }
            bufferReader.close();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        int i = exec.exitValue();
        System.out.println(i);

    }

2. 销毁进程

进程还为我们提供了销毁子进程或进程的方法。虽然,应用程序如何被杀死取决于平台。

让我们看看可能的不同用例。

2.1. 通过引用销毁进程

假设我们正在使用 Windows 操作系统并希望生成记事本应用程序并销毁它。

和以前一样,我们可以使用ProcessBuilder类和start() 方法创建记事本应用程序的实例。

然后我们可以在Process对象上调用destroy() 方法。

    abstract public void destroy();   //摧毁

2.2 按 ID 销毁进程

我们还可以终止在我们的操作系统中运行的进程,这些进程可能不是由我们的应用程序创建的。

执行此操作时应谨慎,因为我们可能会在不知不觉中破坏可能使操作系统不稳定的关键进程。

我们首先需要通过检查任务管理器来找出当前正在运行的进程的进程ID,并找出pid。

        //获取当前进程的pid
        long pid = ProcessHandle.current().pid();
        Optional<ProcessHandle> optionalProcessHandle = ProcessHandle.of(pid);
        optionalProcessHandle.ifPresent(processHandle -> {
            System.out.println(processHandle.pid());
            System.out.println(processHandle.info());
            //进程销毁
            processHandle.destroy();
            
        });

2.3 用武力破坏进程

在执行destroy() 方法时,子进程将被杀死,正如我们在本文前面看到的那样。

destroy() 不起作用的情况下,我们可以选择强制销毁()。

我们应该总是首先从destroy() 方法开始。之后,我们可以通过执行isAlive() 对子进程执行快速检查。

如果它返回 true,则执行destroyForcibly()

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
process.destroy();
if (process.isAlive()) {
    process.destroyForcibly();
}

3. 等待进程完成

我们还有两个重载方法,通过它们我们可以确保我们可以等待一个过程的完成。

3.1. waitFor()

执行此方法时,它将当前执行进程线程置于阻塞等待状态,除非子进程终止。

让我们看一下这个例子:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertThat(process.waitFor() >= 0);
Copy

从上面的例子中我们可以看到,当前线程继续执行,它将继续等待子进程线程结束。子进程结束后,当前线程将继续执行。

3.2.waitFor(1, TimeUnit.SECONDS)

执行此方法时,它将当前执行进程线程置于阻塞等待状态,除非子进程终止或时间不足。

让我们看一下这个例子:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertFalse(process.waitFor(1, TimeUnit.SECONDS));Copy

我们可以从上面的例子中看到,当前线程继续执行,它将继续等待子进程线程结束,或者是否经过了指定的时间间隔。

执行此方法时,如果子进程已退出,它将返回布尔值 true;如果子进程退出之前已经过等待时间,则返回布尔值 false。

4. **exitValue()* *

运行此方法时,当前线程不会等待子进程终止或销毁,但是,如果子进程未终止,它将引发IllegalThreadStateException

另一种解决方法是,如果子进程已成功终止,则会导致进程的退出值。

它可以是任何可能的正整数。

让我们看一个示例,当子进程成功终止时,exitValue() 方法返回一个正整数:

@Test
public void 
  givenSubProcess_whenCurrentThreadWillNotWaitIndefinitelyforSubProcessToEnd_thenProcessExitValueReturnsGrt0() 
  throws IOException {
    ProcessBuilder builder = new ProcessBuilder("notepad.exe");
    Process process = builder.start();
    assertThat(process.exitValue() >= 0);
}Copy

5.isAlive()

当我们想要执行业务处理时,无论进程是否活跃,这都是主观的。

我们可以执行快速检查以查找返回布尔值的进程是否处于活动状态。

让我们看一个简单的例子:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
Thread.sleep(10000);
process.destroy();
assertTrue(process.isAlive());Copy

6. 处理进程流

默认情况下,创建的子进程没有其终端或控制台。其所有标准 I/O(即 stdin、stdout、stderr)操作都将发送到父进程。因此,父进程可以使用这些流向子进程提供输入并从子进程获取输出。

因此,这为我们提供了极大的灵活性,因为它使我们能够控制子进程的输入/输出。

6.1.getErrorStream()

有趣的是,我们可以获取从子进程生成的错误,然后执行业务处理。

之后,我们可以根据我们的要求执行特定的业务进程检查。

让我们看一个例子:

@Test
public void givenSubProcess_whenEncounterError_thenErrorStreamNotNull() throws IOException {
    Process process = Runtime.getRuntime().exec(
      "javac -cp src src\\main\\java\\com\\java9\\process\\ProcessCompilationError.java");
    BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    String errorString = error.readLine();
    assertNotNull(errorString);
}Copy
6.2.getInputStream()

我们还可以获取子进程生成的输出并在父进程中使用,从而允许在进程之间共享信息:

@Test
public void givenSourceProgram_whenReadingInputStream_thenFirstLineEquals3() throws IOException {
    Process process = Runtime.getRuntime().exec(
      "javac -cp src src\\main\\java\\com\\java9\\process\\OutputStreamExample.java");
    process = Runtime.getRuntime()
      .exec("java -cp  src/main/java com.baeldung.java9.process.OutputStreamExample");
    BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
    int value = Integer.parseInt(output.readLine());
 
    assertEquals(3, value);
}Copy
6.3.ge tOutputStream()

我们可以从父进程向子进程发送输入:

Writer w = new OutputStreamWriter(process.getOutputStream(), "UTF-8");
w.write("send to child\n");Copy
8.4. 过滤进程流

这是与选择性运行进程交互的完全有效的用例。

Process为我们提供了根据某个谓词有选择地过滤正在运行的进程的工具。

之后,我们可以在此选择性进程集上执行业务操作:

@Test
public void givenRunningProcesses_whenFilterOnProcessIdRange_thenGetSelectedProcessPid() {
    assertThat(((int) ProcessHandle.allProcesses()
      .filter(ph -> (ph.pid() > 10000 && ph.pid() < 50000))
      .count()) > 0);
}Copy

三、 结论

进程是用于操作系统级别交互的强大类。触发终端命令以及启动、监控和终止应用程序。

参考资料:

posted @ 2024-12-26 15:59  快乐小王子帅气哥哥  阅读(5)  评论(0编辑  收藏  举报

Loading