前言:
上一篇文章(Java高级深度解析之(一)——线程和进程的由浅入深之理论分析)中已经使用两个生动形象的例子说明了何为进程和线程,以及它们有什么特征。这一篇文章的主要篇幅是用于阐述Java如何操作操作系统中的进程和线程。同样,本文将结合理论和实践的方式加以阐述。
-
在操作系统中进程的解读
进程的定义:
在操作系统中关于进程的含义用比较书面的语句是这么描述的:“进程是具有独立功能的程序在某个数据集合上的一次运行活动,也是操作系统对资源分配和保护的基本单位。简而言之,进程就是一个程序的一次执行过程”。
关于进程的解读: 首先进程可以理解为一个程序(比如打开QQ,WeChat等应用程序的同时,就相当于开启了一个进程。),并且这个应用程序是具有独立功能的,要满足进程的前提还必须是:该进程必须要运行在某个数据集合之上。
在《计算机操作系统教程》一书中关于进程概念是这么描述的。
进程的属性
在这个世界上的万事万物皆有其属性,人有吃喝拉撒的属性……。以下描述进程都具有那些基本属性。
动态性:
进程具有生命周期,由创建而产生,由调度而执行,由于某种原因而暂停运行,由撤销而消亡。进程与程序的本质区别就是进程是动态实体,而程序是静态实体,是静止不变的指令序列。
并发性
并发性是指多几个进程实体存在于内存中,在同一时间段内同时运行。并发性是进程的重要特征,也是操作系统的基本特征之一。
独立性
在传统的操作系统中,进程是在计算机系统中独立运行的基本单位,也是独立获得系统资源和独立调度的基本单位。
异步性
因为进程具有并发性与独立性,所以进程一旦被创建,其在计算机系统中推进的速度是不可预知的。即进程是按异步方式运行的,其执行过程具有不可再现性,操作系统必须采取措施对多个并发进程的运行加以协调。
结构特征
从操作系统的实现角度来看,必须采用一个数据结构来表示进程,这个数据结构就是进程控制块。通常,把程序段、数据段、进程控制块三部分叫做进程实体,或称为进程的三要素,在UNIX操作系统中,进程实体被称为“进程映像”。
进程的状态及转换
由于进程是动态的,因此在它的生命周期中,其状态会发生变化。最基本的进程状态有三种。
进程的三种基本状态
运行状态(running)
运行状态也称为执行状态。如果CPU的时间正好被分配给该进程,也就是说该进程的指令正在CPU上运行着,那么这个进程就处于运行状态。在单处理机系统中只有一个CPU,所以任何时刻处于运行状态的进程也就只有一个;而在多处理机系统中,就可以有多个进程同时处于运行状态。
就绪状态(ready)
当进程已经被调入到内存中,所有的运行条件也都满足,只是因为没有将CPU的时间分配给该进程,这时的进程处于就绪状态。处于就绪状态的进程可以有多个,所有能够运行而没有获得CPU的进程都处于就绪状态,它们排成一个或几个队列,称为就绪队列RQ。
等待状态(waiting)
又称为阻塞状态(blocked)。如果运行的进程因发生某事件(如请求系统执行I/O操作)而暂停运行,使得即使处理机空闲,进程也不可能被运行,这样的进程处于等待状态。由于造成等待的原因是各种各样的,因此,处于等待状态的进程通常按不同的条件排队,形成不同的等待队列。
进程状态的转换
进程的三种基本状态之间可以相互转换,转换的原因如下所述。
运行→就绪
处于运行状态的进程在分配给它的时间片运行完毕时,就会变为就绪状态,并被安排进入就绪进程的队列之中。
运行→等待
如果处于运行状态的进程还没有执行完分配给它的时间片,但由于某一种条件(如输入输出的要求、某一种信息的等待等)不满足,导致该进程必须退出运行状态,此时,进程就变为等待状态,同时根据条件被安排进入某一个等待状态之中。
等待→运行
处于等待队列之中的进程,如果引起其等待的条件被满足,它的状态就会变为就绪状态,从等待队列中移出来,而进入就绪队列。处于等待状态的进程不可能直接变为运行状态,进程只能从就绪状态进入运行状态,且只受进程调度程序的挑选。
就绪→运行
处于就绪状态的进程可以转换为运行状态。当CPU出现空闲(处于运行状态的进程因执行完分配给它的时间片或等待某条件而放弃CPU或正常结束)的时候,CPU会转向执行进程调度程序。该程序的任务是按一定的规则从就绪队列中选出一个进程来执行的,这一个被选中的进程的状态就变化为运行状态。
进程的代码实践
在Java中创建线程有两种方式:继承Thread类或者实现Runnable接口再重写run()方法。同理,进程的创建方式也有两种。以下分别阐述进程的创建的调度案例。
Java版本信息
Java中进程抽象类Process的设计
package java.lang;
import java.io.*;
import java.util.concurrent.TimeUnit;
/**
* Java进程抽象类
* @since JDK1.0
*/
public abstract class Process {
/**
* 获取进程的输出流
*/
public abstract OutputStream getOutputStream();
/**
* 获取进程的输入流
*/
public abstract InputStream getInputStream();
/**
* 获取进程的错误流
*/
public abstract InputStream getErrorStream();
/**
* 让进程进入等待状态
*/
public abstract int waitFor() throws InterruptedException;
/**
* 由于这是个抽象类,因此里面同样可以有方法的实现。该方法也是让进程处于等待状态,只是可以设置等待时间和等待的时间单位。
*/
public boolean waitFor(long timeout, TimeUnit unit)
throws InterruptedException
{
long startTime = System.nanoTime();
long rem = unit.toNanos(timeout);
do {
try {
exitValue();
return true;
} catch(IllegalThreadStateException ex) {
if (rem > 0)
Thread.sleep(
Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100));
}
rem = unit.toNanos(timeout) - (System.nanoTime() - startTime);
} while (rem > 0);
return false;
}
/**
* 获取进程的退出标志
*/
public abstract int exitValue();
/**
* 销毁进程(或杀掉进程)
*/
public abstract void destroy();
/**
* 强制杀掉进程
*/
public Process destroyForcibly() {
destroy();
return this;
}
/**
* 判断进程的存活状态
*/
public boolean isAlive() {
try {
exitValue();
return false;
} catch(IllegalThreadStateException e) {
return true;
}
}
}
Java中进程的创建工具类ProcessBuilder
通过ProcessBuilder类来创建进程:
在Java中进程的创建基本上都是依赖于ProcessBuilder工具类来创建的。ProcessBuilder工具类的源码如下:
package java.lang;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* final类不能被继承,没有子类,final类中的方法默认是final的
* final方法不能被子类的方法覆盖,但可以被继承。
* final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
* final不能用于修饰构造方法。
*/
public final class ProcessBuilder
{
private List<String> command;
private File directory;
private Map<String,String> environment;
private boolean redirectErrorStream;
private Redirect[] redirects;
/**
*构造器1: 利用指定的操作系统程序和参数构造一个进程生成器。
* 构造器是将命令参数放进List当中传进去
*/
public ProcessBuilder(List<String> command) {
if (command == null)
throw new NullPointerException();
this.command = command;
}
/**
* 构造器2: 利用指定的操作系统程序和参数构造一个进程生成器。
* 以可边长参数的形式传递参数以创建进程。
*/
public ProcessBuilder(String... command) {
this.command = new ArrayList<>(command.length);
for (String arg : command)
this.command.add(arg);
}
/**
* 返回此进程生成器的操作系统程序和参数。
*/
public ProcessBuilder command(List<String> command) {
if (command == null)
throw new NullPointerException();
this.command = command;
return this;
}
/**
* 设置此进程生成器的操作系统程序和参数。
*/
public ProcessBuilder command(String... command) {
this.command = new ArrayList<>(command.length);
for (String arg : command)
this.command.add(arg);
return this;
}
/**
* 设置此进程生成器的操作系统程序和参数。
*/
public List<String> command() {
return command;
}
/**
* 返回此进程生成器环境的字符串映射视图。 environment方法获得运行进程的环境变量,
* 得到一个Map,可以修改环境变量
*/
public Map<String,String> environment() {
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkPermission(new RuntimePermission("getenv.*"));
if (environment == null)
environment = ProcessEnvironment.environment();
assert environment != null;
return environment;
}
// Only for use by Runtime.exec(...envp...)
ProcessBuilder environment(String[] envp) {
assert environment == null;
if (envp != null) {
environment = ProcessEnvironment.emptyEnvironment(envp.length);
assert environment != null;
for (String envstring : envp) {
// Before 1.5, we blindly passed invalid envstrings
// to the child process.
// We would like to throw an exception, but do not,
// for compatibility with old broken code.
// Silently discard any trailing junk.
if (envstring.indexOf((int) '\u0000') != -1)
envstring = envstring.replaceFirst("\u0000.*", "");
int eqlsign =
envstring.indexOf('=', ProcessEnvironment.MIN_NAME_LENGTH);
// Silently ignore envstrings lacking the required `='.
if (eqlsign != -1)
environment.put(envstring.substring(0,eqlsign),
envstring.substring(eqlsign+1));
}
}
return this;
}
/**
* 返回此进程生成器的工作目录。
*/
public File directory() {
return directory;
}
/**
* 设置此进程生成器的工作目录。
*/
public ProcessBuilder directory(File directory) {
this.directory = directory;
return this;
}
// ---------------- I/O Redirection ----------------
/**
* Implements a <a href="#redirect-output">null input stream</a>.
* 空的输入流
*/
static class NullInputStream extends InputStream {
static final NullInputStream INSTANCE = new NullInputStream();
private NullInputStream() {}
public int read() { return -1; }
public int available() { return 0; }
}
/**
* Implements a <a href="#redirect-input">null output stream</a>.
* 空的输出流
*/
static class NullOutputStream extends OutputStream {
static final NullOutputStream INSTANCE = new NullOutputStream();
private NullOutputStream() {}
public void write(int b) throws IOException {
throw new IOException("Stream closed");
}
}
/**
*
*/
public static abstract class Redirect {
/**
* The type of a {@link Redirect}.
*/
public enum Type {
/**
* The type of {@link Redirect#PIPE Redirect.PIPE}.
*/
PIPE,
/**
* The type of {@link Redirect#INHERIT Redirect.INHERIT}.
*/
INHERIT,
/**
* The type of redirects returned from
* {@link Redirect#from Redirect.from(File)}.
*/
READ,
/**
* The type of redirects returned from
* {@link Redirect#to Redirect.to(File)}.
*/
WRITE,
/**
* The type of redirects returned from
* {@link Redirect#appendTo Redirect.appendTo(File)}.
*/
APPEND
};
/**
* Returns the type of this {@code Redirect}.
* @return the type of this {@code Redirect}
*/
public abstract Type type();
/**
* Indicates that subprocess I/O will be connected to the
* current Java process over a pipe.
*
* This is the default handling of subprocess standard I/O.
*
* <p>It will always be true that
* <pre> {@code
* Redirect.PIPE.file() == null &&
* Redirect.PIPE.type() == Redirect.Type.PIPE
* }</pre>
*/
public static final Redirect PIPE = new Redirect() {
public Type type() { return Type.PIPE; }
public String toString() { return type().toString(); }};
/**
* Indicates that subprocess I/O source or destination will be the
* same as those of the current process. This is the normal
* behavior of most operating system command interpreters (shells).
*
* <p>It will always be true that
* <pre> {@code
* Redirect.INHERIT.file() == null &&
* Redirect.INHERIT.type() == Redirect.Type.INHERIT
* }</pre>
*/
public static final Redirect INHERIT = new Redirect() {
public Type type() { return Type.INHERIT; }
public String toString() { return type().toString(); }};
/**
* Returns the {@link File} source or destination associated
* with this redirect, or {@code null} if there is no such file.
*
* @return the file associated with this redirect,
* or {@code null} if there is no such file
*/
public File file() { return null; }
/**
* When redirected to a destination file, indicates if the output
* is to be written to the end of the file.
*/
boolean append() {
throw new UnsupportedOperationException();
}
/**
* Returns a redirect to read from the specified file.
*
* <p>It will always be true that
* <pre> {@code
* Redirect.from(file).file() == file &&
* Redirect.from(file).type() == Redirect.Type.READ
* }</pre>
*
* @param file The {@code File} for the {@code Redirect}.
* @throws NullPointerException if the specified file is null
* @return a redirect to read from the specified file
*/
public static Redirect from(final File file) {
if (file == null)
throw new NullPointerException();
return new Redirect() {
public Type type() { return Type.READ; }
public File file() { return file; }
public String toString() {
return "redirect to read from file \"" + file + "\"";
}
};
}
/**
* Returns a redirect to write to the specified file.
* If the specified file exists when the subprocess is started,
* its previous contents will be discarded.
*
* <p>It will always be true that
* <pre> {@code
* Redirect.to(file).file() == file &&
* Redirect.to(file).type() == Redirect.Type.WRITE
* }</pre>
*
* @param file The {@code File} for the {@code Redirect}.
* @throws NullPointerException if the specified file is null
* @return a redirect to write to the specified file
*/
public static Redirect to(final File file) {
if (file == null)
throw new NullPointerException();
return new Redirect() {
public Type type() { return Type.WRITE; }
public File file() { return file; }
public String toString() {
return "redirect to write to file \"" + file + "\"";
}
boolean append() { return false; }
};
}
/**
* Returns a redirect to append to the specified file.
* Each write operation first advances the position to the
* end of the file and then writes the requested data.
* Whether the advancement of the position and the writing
* of the data are done in a single atomic operation is
* system-dependent and therefore unspecified.
*
* <p>It will always be true that
* <pre> {@code
* Redirect.appendTo(file).file() == file &&
* Redirect.appendTo(file).type() == Redirect.Type.APPEND
* }</pre>
*
* @param file The {@code File} for the {@code Redirect}.
* @throws NullPointerException if the specified file is null
* @return a redirect to append to the specified file
*/
public static Redirect appendTo(final File file) {
if (file == null)
throw new NullPointerException();
return new Redirect() {
public Type type() { return Type.APPEND; }
public File file() { return file; }
public String toString() {
return "redirect to append to file \"" + file + "\"";
}
boolean append() { return true; }
};
}
/**
* Compares the specified object with this {@code Redirect} for
* equality. Returns {@code true} if and only if the two
* objects are identical or both objects are {@code Redirect}
* instances of the same type associated with non-null equal
* {@code File} instances.
*/
public boolean equals(Object obj) {
if (obj == this)
return true;
if (! (obj instanceof Redirect))
return false;
Redirect r = (Redirect) obj;
if (r.type() != this.type())
return false;
assert this.file() != null;
return this.file().equals(r.file());
}
/**
* Returns a hash code value for this {@code Redirect}.
* @return a hash code value for this {@code Redirect}
*/
public int hashCode() {
File file = file();
if (file == null)
return super.hashCode();
else
return file.hashCode();
}
/**
* No public constructors. Clients must use predefined
* static {@code Redirect} instances or factory methods.
*/
private Redirect() {}
}
private Redirect[] redirects() {
if (redirects == null)
redirects = new Redirect[] {
Redirect.PIPE, Redirect.PIPE, Redirect.PIPE
};
return redirects;
}
/**
* Sets this process builder's standard input source.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method obtain their standard input from this source.
*
* <p>If the source is {@link Redirect#PIPE Redirect.PIPE}
* (the initial value), then the standard input of a
* subprocess can be written to using the output stream
* returned by {@link Process#getOutputStream()}.
* If the source is set to any other value, then
* {@link Process#getOutputStream()} will return a
* <a href="#redirect-input">null output stream</a>.
*
* @param source the new standard input source
* @return this process builder
* @throws IllegalArgumentException
* if the redirect does not correspond to a valid source
* of data, that is, has type
* {@link Redirect.Type#WRITE WRITE} or
* {@link Redirect.Type#APPEND APPEND}
* @since 1.7
*/
public ProcessBuilder redirectInput(Redirect source) {
if (source.type() == Redirect.Type.WRITE ||
source.type() == Redirect.Type.APPEND)
throw new IllegalArgumentException(
"Redirect invalid for reading: " + source);
redirects()[0] = source;
return this;
}
/**
* Sets this process builder's standard output destination.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method send their standard output to this destination.
*
* <p>If the destination is {@link Redirect#PIPE Redirect.PIPE}
* (the initial value), then the standard output of a subprocess
* can be read using the input stream returned by {@link
* Process#getInputStream()}.
* If the destination is set to any other value, then
* {@link Process#getInputStream()} will return a
* <a href="#redirect-output">null input stream</a>.
*
* @param destination the new standard output destination
* @return this process builder
* @throws IllegalArgumentException
* if the redirect does not correspond to a valid
* destination of data, that is, has type
* {@link Redirect.Type#READ READ}
* @since 1.7
*/
public ProcessBuilder redirectOutput(Redirect destination) {
if (destination.type() == Redirect.Type.READ)
throw new IllegalArgumentException(
"Redirect invalid for writing: " + destination);
redirects()[1] = destination;
return this;
}
/**
* Sets this process builder's standard error destination.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method send their standard error to this destination.
*
* <p>If the destination is {@link Redirect#PIPE Redirect.PIPE}
* (the initial value), then the error output of a subprocess
* can be read using the input stream returned by {@link
* Process#getErrorStream()}.
* If the destination is set to any other value, then
* {@link Process#getErrorStream()} will return a
* <a href="#redirect-output">null input stream</a>.
*
* <p>If the {@link #redirectErrorStream redirectErrorStream}
* attribute has been set {@code true}, then the redirection set
* by this method has no effect.
*
* @param destination the new standard error destination
* @return this process builder
* @throws IllegalArgumentException
* if the redirect does not correspond to a valid
* destination of data, that is, has type
* {@link Redirect.Type#READ READ}
* @since 1.7
*/
public ProcessBuilder redirectError(Redirect destination) {
if (destination.type() == Redirect.Type.READ)
throw new IllegalArgumentException(
"Redirect invalid for writing: " + destination);
redirects()[2] = destination;
return this;
}
/**
* Sets this process builder's standard input source to a file.
*
* <p>This is a convenience method. An invocation of the form
* {@code redirectInput(file)}
* behaves in exactly the same way as the invocation
* {@link #redirectInput(Redirect) redirectInput}
* {@code (Redirect.from(file))}.
*
* @param file the new standard input source
* @return this process builder
* @since 1.7
*/
public ProcessBuilder redirectInput(File file) {
return redirectInput(Redirect.from(file));
}
/**
* Sets this process builder's standard output destination to a file.
*
* <p>This is a convenience method. An invocation of the form
* {@code redirectOutput(file)}
* behaves in exactly the same way as the invocation
* {@link #redirectOutput(Redirect) redirectOutput}
* {@code (Redirect.to(file))}.
*
* @param file the new standard output destination
* @return this process builder
* @since 1.7
*/
public ProcessBuilder redirectOutput(File file) {
return redirectOutput(Redirect.to(file));
}
/**
* Sets this process builder's standard error destination to a file.
*
* <p>This is a convenience method. An invocation of the form
* {@code redirectError(file)}
* behaves in exactly the same way as the invocation
* {@link #redirectError(Redirect) redirectError}
* {@code (Redirect.to(file))}.
*
* @param file the new standard error destination
* @return this process builder
* @since 1.7
*/
public ProcessBuilder redirectError(File file) {
return redirectError(Redirect.to(file));
}
/**
* Returns this process builder's standard input source.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method obtain their standard input from this source.
* The initial value is {@link Redirect#PIPE Redirect.PIPE}.
*
* @return this process builder's standard input source
* @since 1.7
*/
public Redirect redirectInput() {
return (redirects == null) ? Redirect.PIPE : redirects[0];
}
/**
* Returns this process builder's standard output destination.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method redirect their standard output to this destination.
* The initial value is {@link Redirect#PIPE Redirect.PIPE}.
*
* @return this process builder's standard output destination
* @since 1.7
*/
public Redirect redirectOutput() {
return (redirects == null) ? Redirect.PIPE : redirects[1];
}
/**
* Returns this process builder's standard error destination.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method redirect their standard error to this destination.
* The initial value is {@link Redirect#PIPE Redirect.PIPE}.
*
* @return this process builder's standard error destination
* @since 1.7
*/
public Redirect redirectError() {
return (redirects == null) ? Redirect.PIPE : redirects[2];
}
/**
* Sets the source and destination for subprocess standard I/O
* to be the same as those of the current Java process.
*
* <p>This is a convenience method. An invocation of the form
* <pre> {@code
* pb.inheritIO()
* }</pre>
* behaves in exactly the same way as the invocation
* <pre> {@code
* pb.redirectInput(Redirect.INHERIT)
* .redirectOutput(Redirect.INHERIT)
* .redirectError(Redirect.INHERIT)
* }</pre>
*
* This gives behavior equivalent to most operating system
* command interpreters, or the standard C library function
* {@code system()}.
*
* @return this process builder
* @since 1.7
*/
public ProcessBuilder inheritIO() {
Arrays.fill(redirects(), Redirect.INHERIT);
return this;
}
/**
* Tells whether this process builder merges standard error and
* standard output.
*
* <p>If this property is {@code true}, then any error output
* generated by subprocesses subsequently started by this object's
* {@link #start()} method will be merged with the standard
* output, so that both can be read using the
* {@link Process#getInputStream()} method. This makes it easier
* to correlate error messages with the corresponding output.
* The initial value is {@code false}.
*
* @return this process builder's {@code redirectErrorStream} property
*/
public boolean redirectErrorStream() {
return redirectErrorStream;
}
/**
* Sets this process builder's {@code redirectErrorStream} property.
*
* <p>If this property is {@code true}, then any error output
* generated by subprocesses subsequently started by this object's
* {@link #start()} method will be merged with the standard
* output, so that both can be read using the
* {@link Process#getInputStream()} method. This makes it easier
* to correlate error messages with the corresponding output.
* The initial value is {@code false}.
*
* @param redirectErrorStream the new property value
* @return this process builder
*/
public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
this.redirectErrorStream = redirectErrorStream;
return this;
}
/**
* Starts a new process using the attributes of this process builder.
*
* <p>The new process will
* invoke the command and arguments given by {@link #command()},
* in a working directory as given by {@link #directory()},
* with a process environment as given by {@link #environment()}.
*
* <p>This method checks that the command is a valid operating
* system command. Which commands are valid is system-dependent,
* but at the very least the command must be a non-empty list of
* non-null strings.
*
* <p>A minimal set of system dependent environment variables may
* be required to start a process on some operating systems.
* As a result, the subprocess may inherit additional environment variable
* settings beyond those in the process builder's {@link #environment()}.
*
* <p>If there is a security manager, its
* {@link SecurityManager#checkExec checkExec}
* method is called with the first component of this object's
* {@code command} array as its argument. This may result in
* a {@link SecurityException} being thrown.
*
* <p>Starting an operating system process is highly system-dependent.
* Among the many things that can go wrong are:
* <ul>
* <li>The operating system program file was not found.
* <li>Access to the program file was denied.
* <li>The working directory does not exist.
* </ul>
*
* <p>In such cases an exception will be thrown. The exact nature
* of the exception is system-dependent, but it will always be a
* subclass of {@link IOException}.
*
* <p>Subsequent modifications to this process builder will not
* affect the returned {@link Process}.
*
* @return a new {@link Process} object for managing the subprocess
*
* @throws NullPointerException
* if an element of the command list is null
*
* @throws IndexOutOfBoundsException
* if the command is an empty list (has size {@code 0})
*
* @throws SecurityException
* if a security manager exists and
* <ul>
*
* <li>its
* {@link SecurityManager#checkExec checkExec}
* method doesn't allow creation of the subprocess, or
*
* <li>the standard input to the subprocess was
* {@linkplain #redirectInput redirected from a file}
* and the security manager's
* {@link SecurityManager#checkRead checkRead} method
* denies read access to the file, or
*
* <li>the standard output or standard error of the
* subprocess was
* {@linkplain #redirectOutput redirected to a file}
* and the security manager's
* {@link SecurityManager#checkWrite checkWrite} method
* denies write access to the file
*
* </ul>
*
* @throws IOException if an I/O error occurs
*
* @see Runtime#exec(String[], String[], java.io.File)
*/
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()]);
cmdarray = cmdarray.clone();
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();
for (int i = 1; i < cmdarray.length; i++) {
if (cmdarray[i].indexOf('\u0000') >= 0) {
throw new IOException("invalid null character in command");
}
}
try {
// 最终返回的是一个进程。因此该类的此方法适用于返回创建的进程。
return ProcessImpl.start(cmdarray,
environment,
dir,
redirects,
redirectErrorStream);
} catch (IOException | IllegalArgumentException e) {
String exceptionInfo = ": " + e.getMessage();
Throwable cause = e;
if ((e instanceof IOException) && security != null) {
// Can not disclose the fail reason for read-protected files.
try {
security.checkRead(prog);
} catch (SecurityException se) {
exceptionInfo = "";
cause = se;
}
}
// 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 + "\")")
+ exceptionInfo,
cause);
}
}
}
在了解了Process类和ProcessBuilder类之后,可以清晰的了解到,Java中可以使用ProcessBuilder类来创建进程。该类提供了两种创建进程的方法(构造器)。
/**
*构造器1: 利用指定的操作系统程序和参数构造一个进程生成器。
* 构造器是将命令参数放进List当中传进去
*/
public ProcessBuilder(List<String> command) {
if (command == null)
throw new NullPointerException();
this.command = command;
}
/**
* 构造器2: 利用指定的操作系统程序和参数构造一个进程生成器。
* 以可边长参数的形式传递参数以创建进程。
*/
public ProcessBuilder(String... command) {
this.command = new ArrayList<>(command.length);
for (String arg : command)
this.command.add(arg);
}
构造器中传递的是需要创建的进程的命令参数,第一个构造器是将命令参数放进List当中传进去,第二构造器是以不定长字符串的形式传进去。
前面提到是通过ProcessBuilder的start方法来创建一个新进程的,我们看一下start方法中具体做了哪些事情。下面是start方法的具体实现源代码:
/**
* Starts a new process using the attributes of this process builder.
*
* <p>The new process will
* invoke the command and arguments given by {@link #command()},
* in a working directory as given by {@link #directory()},
* with a process environment as given by {@link #environment()}.
*
* <p>This method checks that the command is a valid operating
* system command. Which commands are valid is system-dependent,
* but at the very least the command must be a non-empty list of
* non-null strings.
*
* <p>A minimal set of system dependent environment variables may
* be required to start a process on some operating systems.
* As a result, the subprocess may inherit additional environment variable
* settings beyond those in the process builder's {@link #environment()}.
*
* <p>If there is a security manager, its
* {@link SecurityManager#checkExec checkExec}
* method is called with the first component of this object's
* {@code command} array as its argument. This may result in
* a {@link SecurityException} being thrown.
*
* <p>Starting an operating system process is highly system-dependent.
* Among the many things that can go wrong are:
* <ul>
* <li>The operating system program file was not found.
* <li>Access to the program file was denied.
* <li>The working directory does not exist.
* </ul>
*
* <p>In such cases an exception will be thrown. The exact nature
* of the exception is system-dependent, but it will always be a
* subclass of {@link IOException}.
*
* <p>Subsequent modifications to this process builder will not
* affect the returned {@link Process}.
*
* @return a new {@link Process} object for managing the subprocess
*
* @throws NullPointerException
* if an element of the command list is null
*
* @throws IndexOutOfBoundsException
* if the command is an empty list (has size {@code 0})
*
* @throws SecurityException
* if a security manager exists and
* <ul>
*
* <li>its
* {@link SecurityManager#checkExec checkExec}
* method doesn't allow creation of the subprocess, or
*
* <li>the standard input to the subprocess was
* {@linkplain #redirectInput redirected from a file}
* and the security manager's
* {@link SecurityManager#checkRead checkRead} method
* denies read access to the file, or
*
* <li>the standard output or standard error of the
* subprocess was
* {@linkplain #redirectOutput redirected to a file}
* and the security manager's
* {@link SecurityManager#checkWrite checkWrite} method
* denies write access to the file
*
* </ul>
*
* @throws IOException if an I/O error occurs
*
* @see Runtime#exec(String[], String[], java.io.File)
*/
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()]);
cmdarray = cmdarray.clone();
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();
for (int i = 1; i < cmdarray.length; i++) {
if (cmdarray[i].indexOf('\u0000') >= 0) {
throw new IOException("invalid null character in command");
}
}
try {
// 最终返回的是一个进程。因此该类的此方法适用于返回创建的进程。
return ProcessImpl.start(cmdarray,
environment,
dir,
redirects,
redirectErrorStream);
} catch (IOException | IllegalArgumentException e) {
String exceptionInfo = ": " + e.getMessage();
Throwable cause = e;
if ((e instanceof IOException) && security != null) {
// Can not disclose the fail reason for read-protected files.
try {
security.checkRead(prog);
} catch (SecurityException se) {
exceptionInfo = "";
cause = se;
}
}
// 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 + "\")")
+ exceptionInfo,
cause);
}
}
该方法返回一个Process对象,该方法的前面部分相当于是根据命令参数以及设置的工作目录进行一些参数设定,最重要的是try语句块里面的一句:
结合上面的两个图可以清晰的看清楚strat()方法的返回逻辑。该方法确实是创建了进程。
// System-dependent portion of ProcessBuilder.start()
static Process start(String cmdarray[],
java.util.Map<String,String> environment,
String dir,
ProcessBuilder.Redirect[] redirects,
boolean redirectErrorStream)
throws IOException
{
String envblock = ProcessEnvironment.toEnvironmentBlock(environment);
FileInputStream f0 = null;
FileOutputStream f1 = null;
FileOutputStream f2 = null;
try {
long[] stdHandles;
if (redirects == null) {
stdHandles = new long[] { -1L, -1L, -1L };
} else {
stdHandles = new long[3];
if (redirects[0] == Redirect.PIPE)
stdHandles[0] = -1L;
else if (redirects[0] == Redirect.INHERIT)
stdHandles[0] = fdAccess.getHandle(FileDescriptor.in);
else {
f0 = new FileInputStream(redirects[0].file());
stdHandles[0] = fdAccess.getHandle(f0.getFD());
}
if (redirects[1] == Redirect.PIPE)
stdHandles[1] = -1L;
else if (redirects[1] == Redirect.INHERIT)
stdHandles[1] = fdAccess.getHandle(FileDescriptor.out);
else {
f1 = newFileOutputStream(redirects[1].file(),
redirects[1].append());
stdHandles[1] = fdAccess.getHandle(f1.getFD());
}
if (redirects[2] == Redirect.PIPE)
stdHandles[2] = -1L;
else if (redirects[2] == Redirect.INHERIT)
stdHandles[2] = fdAccess.getHandle(FileDescriptor.err);
else {
f2 = newFileOutputStream(redirects[2].file(),
redirects[2].append());
stdHandles[2] = fdAccess.getHandle(f2.getFD());
}
}
return new ProcessImpl(cmdarray, envblock, dir,
stdHandles, redirectErrorStream);
} finally {
// In theory, close() can throw IOException
// (although it is rather unlikely to happen here)
try { if (f0 != null) f0.close(); }
finally {
try { if (f1 != null) f1.close(); }
finally { if (f2 != null) f2.close(); }
}
}
}
说明真正创建进程的是这一句,注意调用的是ProcessImpl类的start方法,此处可以知道start必然是一个静态方法。那么ProcessImpl又是什么类呢?该类同样位于java.lang.ProcessImpl路径下,看一下该类的具体实现:
从图中可以清晰可见ProcessImpl是继承于进程类Process的。这是ProcessImpl类的start方法的具体实现,而事实上start方法中是通过这句来创建一个ProcessImpl对象的:
而在ProcessImpl中对Process类中的几个抽象方法进行了具体实现。说明事实上通过ProcessBuilder的start方法创建的是一个ProcessImpl对象。
下面看一下具体使用ProcessBuilder创建进程的例子,比如我要通过ProcessBuilder来启动一个进程打开cmd,并获取ip地址信息,那么可以这么写:
package test1;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class ProcessUtils {
public static void main(String[] args) {
openCmd1();
}
/**
* 通过创建一个 线程用于打开cmd并查询IP地址:
* 这里使用的是可变参数方式创建
*/
public static void openCmd1(){
try {
ProcessBuilder pb = new ProcessBuilder("cmd","/c","ipconfig/all");
// 这里需要手动抛出IO异常
Process process = pb.start();
// 输出
Scanner scanner = new Scanner(process.getInputStream());
while(scanner.hasNextLine()){
System.out.println(scanner.nextLine());
}
scanner.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
控制台确实输出了如下信息:
同样的可以使用第二种方式(List方式创建进程):最终的结果也是一样的。
/**
* 通过创建一个 线程用于打开cmd并查询IP地址:
* 这里使用的是List传递参数的方式创建,但是命令的顺序必须对应
*/
public static void openCm2(){
try {
List<String> list = new ArrayList<String>();
list.add("cmd");
list.add("/c");
list.add("ipconfig/all");
ProcessBuilder pb = new ProcessBuilder(list);
// 这里需要手动抛出IO异常
Process process = pb.start();
// 输出
Scanner scanner = new Scanner(process.getInputStream());
while(scanner.hasNextLine()){
System.out.println(scanner.nextLine());
}
scanner.close();
} catch (IOException e) {
e.printStackTrace();
}
}
- 通过Runtime的exec方法来创建进程
首先还是来看一下Runtime类和exec方法的具体实现,Runtime,顾名思义,即运行时,表示当前进程所在的虚拟机实例。由于任何进程只会运行于一个虚拟机实例当中,所以在Runtime中采用了单例模式,即只会产生一个虚拟机实例:
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
...
}
从这里可以看出,由于Runtime类的构造器是private的,所以只有通过getRuntime去获取Runtime的实例。接下来着重看一下exec方法 实现,在Runtime中有多个exec的不同重载实现,但真正最后执行的是这个版本的exec方法:
public Process exec(String[] cmdarray, String[] envp, File dir)
throws IOException {
return new ProcessBuilder(cmdarray)
.environment(envp)
.directory(dir)
.start();
}
可以发现,事实上通过Runtime类的exec创建进程的话,最终还是通过ProcessBuilder类的start方法来创建的。下面看一个例子,看一下通过Runtime的exec如何创建进程,还是前面的例子,调用cmd,获取ip地址信息:
/**
* 通过创建一个 线程用于打开cmd并查询IP地址:
* 使用RunTime的形式创建线程
*/
public static void openCm3(){
try {
String cmd = "cmd "+"/c "+"ipconfig/all";
Process process = Runtime.getRuntime().exec(cmd);
Scanner scanner = new Scanner(process.getInputStream());
while(scanner.hasNextLine()){
System.out.println(scanner.nextLine());
}
scanner.close();
} catch (IOException e) {
e.printStackTrace();
}
}
要注意的是,exec方法不支持不定长参数(ProcessBuilder是支持不定长参数的),所以必须先把命令参数拼接好再传进去。关于在Java中如何创建线程和进程的话,暂时就讲这么多了,感兴趣的朋友可以参考相关资料、
此系列的文章还会持续更新...