Java+Linux,深入內(nèi)核源碼講解多線程之進(jìn)程
之前寫了兩篇文章,都是針對(duì)Linux這個(gè)系統(tǒng)的,為什么?我為什么這么喜歡寫這個(gè)系統(tǒng)的知識(shí),可能就是為了今天的內(nèi)容多線程系列,現(xiàn)在多線程不是一個(gè)面試重點(diǎn) 啊,那如果你能深入系統(tǒng)內(nèi)核回答這個(gè)知識(shí)點(diǎn),面試官會(huì)怎么想?你會(huì)不會(huì)占據(jù)面試的主動(dòng)權(quán)(我不會(huì)說今天被一個(gè)面試者驚艷到了的)今天,我就開始一個(gè)系列的內(nèi)容,多線程--高并發(fā),深入的給大家講解,我就不信講不明白這么個(gè)小東西,有問題的地方希望大家能夠指出,謝謝,大家一起成長(zhǎng)
今天我們將第一個(gè)知識(shí)點(diǎn):進(jìn)程
Linux 內(nèi)核如何描述一個(gè)進(jìn)程?
1. Linux 的進(jìn)程
進(jìn)程的術(shù)語是 process,是 Linux 最基礎(chǔ)的抽象,另一個(gè)基礎(chǔ)抽象是文件。
最簡(jiǎn)單的理解,進(jìn)程就是執(zhí)行中 (executing, 不等于running) 的程序。
更準(zhǔn)確一點(diǎn)的理解,進(jìn)程包括執(zhí)行中的程序以及相關(guān)的資源 (包括cpu狀態(tài)、打開的文件、掛起的信號(hào)、tty、內(nèi)存地址空間等)。
一種簡(jiǎn)潔的說法:進(jìn)程 = n*執(zhí)行流 + 資源,n>=1。
Linux 進(jìn)程的特點(diǎn):
- 通過系統(tǒng)調(diào)用 fork() 創(chuàng)建進(jìn)程,fork() 會(huì)復(fù)制現(xiàn)有進(jìn)程來創(chuàng)建一個(gè)全新的進(jìn)程。
- 內(nèi)核里,并不嚴(yán)格區(qū)分進(jìn)程和線程。
- 從內(nèi)核的角度看,調(diào)度單位是線程 (即執(zhí)行流)。可以把線程看做是進(jìn)程里的一條執(zhí)行流,1個(gè)進(jìn)程里可以有1個(gè)或者多個(gè)線程。
- 內(nèi)核里,常把進(jìn)程稱為 task 或者 thread,這樣描述更準(zhǔn)確,因?yàn)樵S多進(jìn)程就只有1條執(zhí)行流。
- 內(nèi)核通過輕量級(jí)進(jìn)程 (lightweight process) 來支持多線程。1個(gè)輕量級(jí)進(jìn)程就對(duì)應(yīng)1個(gè)線程,輕量級(jí)進(jìn)程之間可以共享打開的文件、地址空間等資源。
2. Linux 的進(jìn)程描述符
2.1 task_struct
內(nèi)核里,通過 task_struct 結(jié)構(gòu)體來描述一個(gè)進(jìn)程,稱為進(jìn)程描述符 (process descriptor),它保存著支撐一個(gè)進(jìn)程正常運(yùn)行的所有信息。
每一個(gè)進(jìn)程,即便是輕量級(jí)進(jìn)程(即線程),都有1個(gè) task_struct。
- sched.h (include\linux)
- struct task_struct {
- struct thread_info thread_info;
- volatile long state;
- void *stack;
- [...]
- struct mm_struct *mm;
- [...]
- pid_t pid;
- [...]
- struct task_struct *parent;
- [...]
- char comm[TASK_COMM_LEN];
- [...]
- struct files_struct *files;
- [...]
- struct signal_struct *signal;
- }
這是一個(gè)龐大的結(jié)構(gòu)體,不僅有許多進(jìn)程相關(guān)的基礎(chǔ)字段,還有許多指向其他數(shù)據(jù)結(jié)構(gòu)的指針。
它包含的字段能完整地描述一個(gè)正在執(zhí)行的程序,包括 cpu 狀態(tài)、打開的文件、地址空間、掛起的信號(hào)、進(jìn)程狀態(tài)等。

作為初學(xué)者,先簡(jiǎn)單地了解部分字段就好::
- struct thread_info thread_info: 進(jìn)程底層信息,平臺(tái)相關(guān),下面會(huì)詳細(xì)描述。
- long state: 進(jìn)程當(dāng)前的狀態(tài),下面是幾個(gè)比較重要的進(jìn)程狀態(tài)以及它們之間的轉(zhuǎn)換流程。

- void *stack: 指向進(jìn)程內(nèi)核棧,下面會(huì)解釋。
- struct mm_struct *mm: 與進(jìn)程地址空間相關(guān)的信息都保存在一個(gè)叫內(nèi)存描述符 (memory descriptor) 的結(jié)構(gòu)體 (mm_struct) 中。

pid_t pid: 進(jìn)程標(biāo)識(shí)符,本質(zhì)就是一個(gè)數(shù)字,是用戶空間引用進(jìn)程的唯一標(biāo)識(shí)。
- struct task_struct *parent: 父進(jìn)程的 task_struct。
- char comm[TASK_COMM_LEN]: 進(jìn)程的名稱。
- struct files_struct *files: 打開的文件表。
- struct signal_struct *signal: 信號(hào)處理相關(guān)。
其他字段,等到有需要的時(shí)候再回過頭來學(xué)習(xí)。
2.2 當(dāng)發(fā)生系統(tǒng)調(diào)用或者進(jìn)程切換時(shí),內(nèi)核如何找到 task_struct ?
對(duì)于 ARM 架構(gòu),答案是:通過內(nèi)核棧 (kernel mode stack)。
為什么要有內(nèi)核棧?
因?yàn)閮?nèi)核是可重入的,在內(nèi)核中會(huì)有多條與不同進(jìn)程相關(guān)聯(lián)的執(zhí)行路徑。因此不同的進(jìn)程處于內(nèi)核態(tài)時(shí),都需要有自己私有的進(jìn)程內(nèi)核棧 (process kernel stack)。
當(dāng)進(jìn)程從用戶態(tài)切換到內(nèi)核態(tài)時(shí),所使用的棧會(huì)從用戶棧切換到內(nèi)核棧。
至于是如何切換的,關(guān)鍵詞是系統(tǒng)調(diào)用,這不是本文關(guān)注的重點(diǎn),先放一邊,學(xué)習(xí)內(nèi)核要懂得恰當(dāng)?shù)臅r(shí)候忽略細(xì)節(jié)。
當(dāng)發(fā)生進(jìn)程切換時(shí),也會(huì)切換到目標(biāo)進(jìn)程的內(nèi)核棧。
同上,關(guān)鍵詞是硬件上下文切換 (hardware context switch),忽略具體實(shí)現(xiàn)。
無論何時(shí),只要進(jìn)程處于內(nèi)核態(tài),就會(huì)有內(nèi)核棧可以使用,否則系統(tǒng)就離崩潰不遠(yuǎn)了。
ARM 架構(gòu)的內(nèi)核棧和 task_struct 的關(guān)系如下:

內(nèi)核棧的長(zhǎng)度是 THREAD_SIZE,對(duì)于 ARM 架構(gòu),一般是 2 個(gè)頁框的大小,即 8KB。
內(nèi)核將一個(gè)較小的數(shù)據(jù)結(jié)構(gòu) thread_info 放在內(nèi)核棧的底部,它負(fù)責(zé)將內(nèi)核棧和 task_struct 串聯(lián)起來。thread_info 是平臺(tái)相關(guān)的,在 ARM 架構(gòu)中的定義如下:
- // thread_info.h (arch\arm\include\asm)
- struct thread_info {
- unsigned long flags; /* low level flags */
- int preempt_count; /* 0 => preemptable, <0 => bug */
- mm_segment_t addr_limit; /* address limit */
- struct task_struct *task; /* main task structure */
- [...]
- struct cpu_context_save cpu_context; /* cpu context */
- [...]
- };
thread_info 保存了一個(gè)進(jìn)程能被調(diào)度執(zhí)行的最底層信息(low level task data),例如struct cpu_context_save cpu_context 會(huì)在進(jìn)程切換時(shí)用來保存/恢復(fù)寄存器上下文。
內(nèi)核通過內(nèi)核棧的棧指針可以快速地拿到 thread_info:
- // thread_info.h (include\linux)
- static inline struct thread_info *current_thread_info(void)
- {
- // current_stack_pointer 是當(dāng)前進(jìn)程內(nèi)核棧的棧指針
- return (struct thread_info *)
- (current_stack_pointer & ~(THREAD_SIZE - 1));
- }
- 然后通過 thread_info 找到 task_struct:
- // current.h (include\asm-generic)
- #define current (current_thread_info()->task)
內(nèi)核里通過 current 宏可以獲得當(dāng)前進(jìn)程的 task_struct。
2.3 task_struct 的分配和初始化
當(dāng)上層應(yīng)用使用 fork() 創(chuàng)建進(jìn)程時(shí),內(nèi)核會(huì)新建一個(gè) task_struct。
進(jìn)程的創(chuàng)建是個(gè)復(fù)雜的工作,可以延伸出無數(shù)的細(xì)節(jié)。這里我們只是簡(jiǎn)單地了解一下 task_struct 的分配和部分初始化的流程。
fork() 在內(nèi)核里的核心流程:

dup_task_struct() 做了什么?

至于設(shè)置內(nèi)核棧里做了什么,涉及到了進(jìn)程的創(chuàng)建與切換,不在本文的關(guān)注范圍內(nèi),以后再研究了。
3. 實(shí)驗(yàn):打印 task_struct / thread_info / kernel mode stack
實(shí)驗(yàn)?zāi)康模?/strong>
- 梳理 task_struct / thread_info / kernel mode stack 的關(guān)系。
實(shí)驗(yàn)代碼:
- 實(shí)驗(yàn)代碼:
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/sched.h>
- static void print_task_info(struct task_struct *task)
- {
- printk(KERN_NOTICE "%10s %5d task_struct (%p) / stack(%p~%p) / thread_info->task (%p)",
- task->comm,
- task->pid,
- task,
- task->stack,
- ((unsigned long *)task->stack) + THREAD_SIZE,
- task_thread_info(task)->task);
- }
- static int __init task_init(void)
- {
- struct task_struct *task = current;
- printk(KERN_INFO "task module init\n");
- print_task_info(task);
- do {
- task = task->parent;
- print_task_info(task);
- } while (task->pid != 0);
- return 0;
- }
- module_init(task_init);
- static void __exit task_exit(void)
- {
- printk(KERN_INFO "task module exit\n ");
- }
- module_exit(task_exit);
運(yùn)行效果:
- task module init
- insmod 3123 task_struct (edb42580) / stack(ed46c000~ed474000) / thread_info->task (edb42580)
- bash 2393 task_struct (eda13e80) / stack(c9dda000~c9de2000) / thread_info->task (eda13e80)
- sshd 2255 task_struct (ee5c9f40) / stack(c9d2e000~c9d36000) / thread_info->task (ee5c9f40)
- sshd 543 task_struct (ef15f080) / stack(ee554000~ee55c000) / thread_info->task (ef15f080)
- systemd 1 task_struct (ef058000) / stack(ef04c000~ef054000) / thread_info->task (ef058000)
在程序里,我們通過 task_struct 找到 stack,然后通過 stack 找到 thread_info,最后又通過 thread_info->task 找到 task_struct。
到這里,不知道你對(duì)進(jìn)程的概念是不是有了一個(gè)清晰的理解
但是上面是通過Linux進(jìn)行了線程的展示,在日常的工作中,代碼的實(shí)現(xiàn)和編寫我們還是以Java為主,那我們來看一下Java進(jìn)程
1.Java進(jìn)程的創(chuàng)建
Java提供了兩種方法用來啟動(dòng)進(jìn)程或其它程序:
- 使用Runtime的exec()方法
- 使用ProcessBuilder的start()方法
1.1 ProcessBuilder
ProcessBuilder類是J2SE 1.5在java.lang中新添加的一個(gè)新類,此類用于創(chuàng)建操作系統(tǒng)進(jìn)程,它提供一種啟動(dòng)和管理進(jìn)程(也就是應(yīng)用程序)的方法。在J2SE 1.5之前,都是由Process類處來實(shí)現(xiàn)進(jìn)程的控制管理。
每個(gè) ProcessBuilder 實(shí)例管理一個(gè)進(jìn)程屬性集。start() 方法利用這些屬性創(chuàng)建一個(gè)新的 Process 實(shí)例。start() 方法可以從同一實(shí)例重復(fù)調(diào)用,以利用相同的或相關(guān)的屬性創(chuàng)建新的子進(jìn)程。
每個(gè)進(jìn)程生成器管理這些進(jìn)程屬性:
- 命令 是一個(gè)字符串列表,它表示要調(diào)用的外部程序文件及其參數(shù)(如果有)。在此,表示有效的操作系統(tǒng)命令的字符串列表是依賴于系統(tǒng)的。例如,每一個(gè)總體變量,通常都要成為此列表中的元素,但有一些操作系統(tǒng),希望程序能自己標(biāo)記命令行字符串——在這種系統(tǒng)中,Java 實(shí)現(xiàn)可能需要命令確切地包含這兩個(gè)元素。
- 環(huán)境 是從變量 到值 的依賴于系統(tǒng)的映射。初始值是當(dāng)前進(jìn)程環(huán)境的一個(gè)副本(請(qǐng)參閱 System.getenv())。
- 工作目錄。默認(rèn)值是當(dāng)前進(jìn)程的當(dāng)前工作目錄,通常根據(jù)系統(tǒng)屬性 user.dir 來命名。
- redirectErrorStream 屬性。最初,此屬性為 false,意思是子進(jìn)程的標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出被發(fā)送給兩個(gè)獨(dú)立的流,這些流可以通過 Process.getInputStream() 和 Process.getErrorStream() 方法來訪問。如果將值設(shè)置為 true,標(biāo)準(zhǔn)錯(cuò)誤將與標(biāo)準(zhǔn)輸出合并。這使得關(guān)聯(lián)錯(cuò)誤消息和相應(yīng)的輸出變得更容易。在此情況下,合并的數(shù)據(jù)可從 Process.getInputStream() 返回的流讀取,而從 Process.getErrorStream() 返回的流讀取將直接到達(dá)文件尾。
修改進(jìn)程構(gòu)建器的屬性將影響后續(xù)由該對(duì)象的 start() 方法啟動(dòng)的進(jìn)程,但從不會(huì)影響以前啟動(dòng)的進(jìn)程或 Java 自身的進(jìn)程。大多數(shù)錯(cuò)誤檢查由 start() 方法執(zhí)行。可以修改對(duì)象的狀態(tài),但這樣 start() 將會(huì)失敗。例如,將命令屬性設(shè)置為一個(gè)空列表將不會(huì)拋出異常,除非包含了 start()。
注意,此類不是同步的。如果多個(gè)線程同時(shí)訪問一個(gè) ProcessBuilder,而其中至少一個(gè)線程從結(jié)構(gòu)上修改了其中一個(gè)屬性,它必須 保持外部同步。
構(gòu)造方法摘要
- ProcessBuilder(List command)
- 利用指定的操作系統(tǒng)程序和參數(shù)構(gòu)造一個(gè)進(jìn)程生成器。
- ProcessBuilder(String... command)
- 利用指定的操作系統(tǒng)程序和參數(shù)構(gòu)造一個(gè)進(jìn)程生成器。
方法摘要
- List command()
- 返回此進(jìn)程生成器的操作系統(tǒng)程序和參數(shù)。
- ProcessBuilder command(List command)
- 設(shè)置此進(jìn)程生成器的操作系統(tǒng)程序和參數(shù)。
- ProcessBuilder command(String... command)
- 設(shè)置此進(jìn)程生成器的操作系統(tǒng)程序和參數(shù)。
- File directory()
- 返回此進(jìn)程生成器的工作目錄。
- ProcessBuilder directory(File directory)
- 設(shè)置此進(jìn)程生成器的工作目錄。
- Map environment()
- 返回此進(jìn)程生成器環(huán)境的字符串映射視圖。
- boolean redirectErrorStream()
- 通知進(jìn)程生成器是否合并標(biāo)準(zhǔn)錯(cuò)誤和標(biāo)準(zhǔn)輸出。
- ProcessBuilder redirectErrorStream(boolean redirectErrorStream)
- 設(shè)置此進(jìn)程生成器的 redirectErrorStream 屬性。
- Process start()
- 使用此進(jìn)程生成器的屬性啟動(dòng)一個(gè)新進(jìn)程。
1.2 Runtime
每個(gè) Java 應(yīng)用程序都有一個(gè) Runtime 類實(shí)例,使應(yīng)用程序能夠與其運(yùn)行的環(huán)境相連接。可以通過 getRuntime 方法獲取當(dāng)前運(yùn)行時(shí)。
應(yīng)用程序不能創(chuàng)建自己的 Runtime 類實(shí)例。但可以通過 getRuntime 方法獲取當(dāng)前Runtime運(yùn)行時(shí)對(duì)象的引用。一旦得到了一個(gè)當(dāng)前的Runtime對(duì)象的引用,就可以調(diào)用Runtime對(duì)象的方法去控制Java虛擬機(jī)的狀態(tài)和行為。
Java代碼 收藏代碼
- void addShutdownHook(Thread hook)
- 注冊(cè)新的虛擬機(jī)來關(guān)閉掛鉤。
- int availableProcessors()
- 向 Java 虛擬機(jī)返回可用處理器的數(shù)目。
- Process exec(String command)
- 在單獨(dú)的進(jìn)程中執(zhí)行指定的字符串命令。
- Process exec(String[] cmdarray)
- 在單獨(dú)的進(jìn)程中執(zhí)行指定命令和變量。
- Process exec(String[] cmdarray, String[] envp)
- 在指定環(huán)境的獨(dú)立進(jìn)程中執(zhí)行指定命令和變量。
- Process exec(String[] cmdarray, String[] envp, File dir)
- 在指定環(huán)境和工作目錄的獨(dú)立進(jìn)程中執(zhí)行指定的命令和變量。
- Process exec(String command, String[] envp)
- 在指定環(huán)境的單獨(dú)進(jìn)程中執(zhí)行指定的字符串命令。
- Process exec(String command, String[] envp, File dir)
- 在有指定環(huán)境和工作目錄的獨(dú)立進(jìn)程中執(zhí)行指定的字符串命令。
- void exit(int status)
- 通過啟動(dòng)虛擬機(jī)的關(guān)閉序列,終止當(dāng)前正在運(yùn)行的 Java 虛擬機(jī)。
- long freeMemory()
- 返回 Java 虛擬機(jī)中的空閑內(nèi)存量。
- void gc()
- 運(yùn)行垃圾回收器。
- InputStream getLocalizedInputStream(InputStream in)
- 已過時(shí)。 從 JDK 1.1 開始,將本地編碼字節(jié)流轉(zhuǎn)換為 Unicode 字符流的首選方法是使用 InputStreamReader 和 BufferedReader 類。
- OutputStream getLocalizedOutputStream(OutputStream out)
- 已過時(shí)。 從 JDK 1.1 開始,將 Unicode 字符流轉(zhuǎn)換為本地編碼字節(jié)流的首選方法是使用 OutputStreamWriter、BufferedWriter 和 PrintWriter 類。
- static Runtime getRuntime()
- 返回與當(dāng)前 Java 應(yīng)用程序相關(guān)的運(yùn)行時(shí)對(duì)象。
- void halt(int status)
- 強(qiáng)行終止目前正在運(yùn)行的 Java 虛擬機(jī)。
- void load(String filename)
- 加載作為動(dòng)態(tài)庫(kù)的指定文件名。
- void loadLibrary(String libname)
- 加載具有指定庫(kù)名的動(dòng)態(tài)庫(kù)。
- long maxMemory()
- 返回 Java 虛擬機(jī)試圖使用的最大內(nèi)存量。
- boolean removeShutdownHook(Thread hook)
- 取消注冊(cè)某個(gè)先前已注冊(cè)的虛擬機(jī)關(guān)閉掛鉤。
- void runFinalization()
- 運(yùn)行掛起 finalization 的所有對(duì)象的終止方法。
- static void runFinalizersOnExit(boolean value)
- 已過時(shí)。 此方法本身具有不安全性。它可能對(duì)正在使用的對(duì)象調(diào)用終結(jié)方法,而其他線程正在操作這些對(duì)象,從而導(dǎo)致不正確的行為或死鎖。
- long totalMemory()
- 返回 Java 虛擬機(jī)中的內(nèi)存總量。
- void traceInstructions(boolean on)
- 啟用/禁用指令跟蹤。
- void traceMethodCalls(boolean on)
- 啟用/禁用方法調(diào)用跟蹤。
1.3 Process
不管通過哪種方法啟動(dòng)進(jìn)程后,都會(huì)返回一個(gè)Process類的實(shí)例代表啟動(dòng)的進(jìn)程,該實(shí)例可用來控制進(jìn)程并獲得相關(guān)信息。Process 類提供了執(zhí)行從進(jìn)程輸入、執(zhí)行輸出到進(jìn)程、等待進(jìn)程完成、檢查進(jìn)程的退出狀態(tài)以及銷毀(殺掉)進(jìn)程的方法:
- void destroy()
- 殺掉子進(jìn)程。
- 一般情況下,該方法并不能殺掉已經(jīng)啟動(dòng)的進(jìn)程,不用為好。
- int exitValue()
- 返回子進(jìn)程的出口值。
- 只有啟動(dòng)的進(jìn)程執(zhí)行完成、或者由于異常退出后,exitValue()方法才會(huì)有正常的返回值,否則拋出異常。
- InputStream getErrorStream()
- 獲取子進(jìn)程的錯(cuò)誤流。
- 如果錯(cuò)誤輸出被重定向,則不能從該流中讀取錯(cuò)誤輸出。
- InputStream getInputStream()
- 獲取子進(jìn)程的輸入流。
- 可以從該流中讀取進(jìn)程的標(biāo)準(zhǔn)輸出。
- OutputStream getOutputStream()
- 獲取子進(jìn)程的輸出流。
- 寫入到該流中的數(shù)據(jù)作為進(jìn)程的標(biāo)準(zhǔn)輸入。
- int waitFor()
- 導(dǎo)致當(dāng)前線程等待,如有必要,一直要等到由該 Process 對(duì)象表示的進(jìn)程已經(jīng)終止。
2.多進(jìn)程編程實(shí)例
一般我們?cè)趈ava中運(yùn)行其它類中的方法時(shí),無論是靜態(tài)調(diào)用,還是動(dòng)態(tài)調(diào)用,都是在當(dāng)前的進(jìn)程中執(zhí)行的,也就是說,只有一個(gè)java虛擬機(jī)實(shí)例在運(yùn)行。而有的時(shí)候,我們需要通過java代碼啟動(dòng)多個(gè)java子進(jìn)程。這樣做雖然占用了一些系統(tǒng)資源,但會(huì)使程序更加穩(wěn)定,因?yàn)樾聠?dòng)的程序是在不同的虛擬機(jī)進(jìn)程中運(yùn)行的,如果有一個(gè)進(jìn)程發(fā)生異常,并不影響其它的子進(jìn)程。
在Java中我們可以使用兩種方法來實(shí)現(xiàn)這種要求。最簡(jiǎn)單的方法就是通過Runtime中的exec方法執(zhí)行java classname。如果執(zhí)行成功,這個(gè)方法返回一個(gè)Process對(duì)象,如果執(zhí)行失敗,將拋出一個(gè)IOException錯(cuò)誤。下面讓我們來看一個(gè)簡(jiǎn)單的例子。
- // Test1.java文件
- import java.io.*;
- public class Test
- {
- public static void main(String[] args)
- {
- FileOutputStream fOut = new FileOutputStream("c:\\Test1.txt");
- fOut.close();
- System.out.println("被調(diào)用成功!");
- }
- }
- // Test_Exec.java
- public class Test_Exec
- {
- public static void main(String[] args)
- {
- Runtime run = Runtime.getRuntime();
- Process p = run.exec("java test1");
- }
- }
通過java Test_Exec運(yùn)行程序后,發(fā)現(xiàn)在C盤多了個(gè)Test1.txt文件,但在控制臺(tái)中并未出現(xiàn)"被調(diào)用成功!"的輸出信息。因此可以斷定,Test已經(jīng)被執(zhí)行成功,但因?yàn)槟撤N原因,Test的輸出信息未在Test_Exec的控制臺(tái)中輸出。這個(gè)原因也很簡(jiǎn)單,因?yàn)槭褂胑xec建立的是Test_Exec的子進(jìn)程,這個(gè)子進(jìn)程并沒有自己的控制臺(tái),因此,它并不會(huì)輸出任何信息。
如果要輸出子進(jìn)程的輸出信息,可以通過Process中的getInputStream得到子進(jìn)程的輸出流(在子進(jìn)程中輸出,在父進(jìn)程中就是輸入),然后將子進(jìn)程中的輸出流從父進(jìn)程的控制臺(tái)輸出。具體的實(shí)現(xiàn)代碼如下如示:
- // Test_Exec_Out.java
- import java.io.*;
- public class Test_Exec_Out
- {
- public static void main(String[] args)
- {
- Runtime run = Runtime.getRuntime();
- Process p = run.exec("java test1");
- BufferedInputStream in = new BufferedInputStream(p.getInputStream());
- BufferedReader br = new BufferedReader(new InputStreamReader(in));
- String s;
- while ((s = br.readLine()) != null)
- System.out.println(s);
- }
- }
從上面的代碼可以看出,在Test_Exec_Out.java中通過按行讀取子進(jìn)程的輸出信息,然后在Test_Exec_Out中按每行進(jìn)行輸出。 上面討論的是如何得到子進(jìn)程的輸出信息。那么,除了輸出信息,還有輸入信息。既然子進(jìn)程沒有自己的控制臺(tái),那么輸入信息也得由父進(jìn)程提供。我們可以通過Process的getOutputStream方法來為子進(jìn)程提供輸入信息(即由父進(jìn)程向子進(jìn)程輸入信息,而不是由控制臺(tái)輸入信息)。我們可以看看如下的代碼:
- // Test2.java文件
- import java.io.*;
- public class Test
- {
- public static void main(String[] args)
- {
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- System.out.println("由父進(jìn)程輸入的信息:" + br.readLine());
- }
- }
- // Test_Exec_In.java
- import java.io.*;
- public class Test_Exec_In
- {
- public static void main(String[] args)
- {
- Runtime run = Runtime.getRuntime();
- Process p = run.exec("java test2");
- BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
- bw.write("向子進(jìn)程輸出信息");
- bw.flush();
- bw.close(); // 必須得關(guān)閉流,否則無法向子進(jìn)程中輸入信息
- // System.in.read();
- }
- }
從以上代碼可以看出,Test1得到由Test_Exec_In發(fā)過來的信息,并將其輸出。當(dāng)你不加bw.flash()和bw.close()時(shí),信息將無法到達(dá)子進(jìn)程,也就是說子進(jìn)程進(jìn)入阻塞狀態(tài),但由于父進(jìn)程已經(jīng)退出了,因此,子進(jìn)程也跟著退出了。如果要證明這一點(diǎn),可以在最后加上System.in.read(),然后通過任務(wù)管理器(在windows下)查看java進(jìn)程,你會(huì)發(fā)現(xiàn)如果加上bw.flush()和bw.close(),只有一個(gè)java進(jìn)程存在,如果去掉它們,就有兩個(gè)java進(jìn)程存在。這是因?yàn)椋绻麑⑿畔鹘oTest2,在得到信息后,Test2就退出了。在這里有一點(diǎn)需要說明一下,exec的執(zhí)行是異步的,并不會(huì)因?yàn)閳?zhí)行的某個(gè)程序阻塞而停止執(zhí)行下面的代碼。因此,可以在運(yùn)行test2后,仍可以執(zhí)行下面的代碼。
exec方法經(jīng)過了多次的重載。上面使用的只是它的一種重載。它還可以將命令和參數(shù)分開,如exec("java.test2")可以寫成exec("java", "test2")。exec還可以通過指定的環(huán)境變量運(yùn)行不同配置的java虛擬機(jī)。
除了使用Runtime的exec方法建立子進(jìn)程外,還可以通過ProcessBuilder建立子進(jìn)程。ProcessBuilder的使用方法如下:
- // Test_Exec_Out.java
- import java.io.*;
- public class Test_Exec_Out
- {
- public static void main(String[] args)
- {
- ProcessBuilder pb = new ProcessBuilder("java", "test1");
- Process p = pb.start();
- … …
- }
- }
在建立子進(jìn)程上,ProcessBuilder和Runtime類似,不同的ProcessBuilder使用start()方法啟動(dòng)子進(jìn)程,而Runtime使用exec方法啟動(dòng)子進(jìn)程。得到Process后,它們的操作就完全一樣的。
ProcessBuilder和Runtime一樣,也可設(shè)置可執(zhí)行文件的環(huán)境信息、工作目錄等。下面的例子描述了如何使用ProcessBuilder設(shè)置這些信息。
- ProcessBuilder pb = new ProcessBuilder("Command", "arg2", "arg2", ''');
- // 設(shè)置環(huán)境變量
- Map<String, String> env = pb.environment();
- env.put("key1", "value1");
- env.remove("key2");
- env.put("key2", env.get("key1") + "_test");
- pb.directory("..\abcd"); // 設(shè)置工作目錄
- Process p = pb.start(); // 建立子進(jìn)程