自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java+Linux,深入內(nèi)核源碼講解多線程之進(jìn)程

開發(fā) 后端
今天,我就開始一個系列的內(nèi)容,多線程--高并發(fā),深入的給大家講解,我就不信講不明白這么個小東西,有問題的地方希望大家能夠指出,謝謝,大家一起成長。

之前寫了兩篇文章,都是針對Linux這個系統(tǒng)的,為什么?我為什么這么喜歡寫這個系統(tǒng)的知識,可能就是為了今天的內(nèi)容多線程系列,現(xiàn)在多線程不是一個面試重點 啊,那如果你能深入系統(tǒng)內(nèi)核回答這個知識點,面試官會怎么想?你會不會占據(jù)面試的主動權(quán)(我不會說今天被一個面試者驚艷到了的)今天,我就開始一個系列的內(nèi)容,多線程--高并發(fā),深入的給大家講解,我就不信講不明白這么個小東西,有問題的地方希望大家能夠指出,謝謝,大家一起成長

今天我們將第一個知識點:進(jìn)程

Linux 內(nèi)核如何描述一個進(jìn)程?

1. Linux 的進(jìn)程

進(jìn)程的術(shù)語是 process,是 Linux 最基礎(chǔ)的抽象,另一個基礎(chǔ)抽象是文件。

最簡單的理解,進(jìn)程就是執(zhí)行中 (executing, 不等于running) 的程序。

更準(zhǔn)確一點的理解,進(jìn)程包括執(zhí)行中的程序以及相關(guān)的資源 (包括cpu狀態(tài)、打開的文件、掛起的信號、tty、內(nèi)存地址空間等)。

一種簡潔的說法:進(jìn)程 = n*執(zhí)行流 + 資源,n>=1。

Linux 進(jìn)程的特點:

  1. 通過系統(tǒng)調(diào)用 fork() 創(chuàng)建進(jìn)程,fork() 會復(fù)制現(xiàn)有進(jìn)程來創(chuàng)建一個全新的進(jìn)程。
  2. 內(nèi)核里,并不嚴(yán)格區(qū)分進(jìn)程和線程。
  3. 從內(nèi)核的角度看,調(diào)度單位是線程 (即執(zhí)行流)??梢园丫€程看做是進(jìn)程里的一條執(zhí)行流,1個進(jìn)程里可以有1個或者多個線程。
  4. 內(nèi)核里,常把進(jìn)程稱為 task 或者 thread,這樣描述更準(zhǔn)確,因為許多進(jìn)程就只有1條執(zhí)行流。
  5. 內(nèi)核通過輕量級進(jìn)程 (lightweight process) 來支持多線程。1個輕量級進(jìn)程就對應(yīng)1個線程,輕量級進(jìn)程之間可以共享打開的文件、地址空間等資源。

2. Linux 的進(jìn)程描述符

2.1 task_struct

內(nèi)核里,通過 task_struct 結(jié)構(gòu)體來描述一個進(jìn)程,稱為進(jìn)程描述符 (process descriptor),它保存著支撐一個進(jìn)程正常運行的所有信息。

每一個進(jìn)程,即便是輕量級進(jìn)程(即線程),都有1個 task_struct。

 

  1. sched.h (include\linux)  
  2. struct task_struct {  
  3. struct thread_info thread_info;  
  4. volatile long state;  
  5. void *stack;  
  6. [...]  
  7. struct mm_struct *mm;  
  8. [...]  
  9. pid_t pid;  
  10. [...]  
  11. struct task_struct *parent;  
  12. [...]  
  13. char comm[TASK_COMM_LEN];  
  14. [...]  
  15. struct files_struct *files;  
  16. [...]  
  17. struct signal_struct *signal;  

這是一個龐大的結(jié)構(gòu)體,不僅有許多進(jìn)程相關(guān)的基礎(chǔ)字段,還有許多指向其他數(shù)據(jù)結(jié)構(gòu)的指針。

它包含的字段能完整地描述一個正在執(zhí)行的程序,包括 cpu 狀態(tài)、打開的文件、地址空間、掛起的信號、進(jìn)程狀態(tài)等。

 

Java+Linux,深入內(nèi)核源碼講解多線程之進(jìn)程

作為初學(xué)者,先簡單地了解部分字段就好::

  • struct thread_info thread_info: 進(jìn)程底層信息,平臺相關(guān),下面會詳細(xì)描述。
  • long state: 進(jìn)程當(dāng)前的狀態(tài),下面是幾個比較重要的進(jìn)程狀態(tài)以及它們之間的轉(zhuǎn)換流程。

 

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

 

Java+Linux,深入內(nèi)核源碼講解多線程之進(jìn)程

pid_t pid: 進(jìn)程標(biāo)識符,本質(zhì)就是一個數(shù)字,是用戶空間引用進(jìn)程的唯一標(biāo)識。

 

  1. struct task_struct *parent: 父進(jìn)程的 task_struct。  
  2. char comm[TASK_COMM_LEN]: 進(jìn)程的名稱。  
  3. struct files_struct *files: 打開的文件表。  
  4. struct signal_struct *signal: 信號處理相關(guān)。 

其他字段,等到有需要的時候再回過頭來學(xué)習(xí)。

2.2 當(dāng)發(fā)生系統(tǒng)調(diào)用或者進(jìn)程切換時,內(nèi)核如何找到 task_struct ?

對于 ARM 架構(gòu),答案是:通過內(nèi)核棧 (kernel mode stack)。

為什么要有內(nèi)核棧?

因為內(nèi)核是可重入的,在內(nèi)核中會有多條與不同進(jìn)程相關(guān)聯(lián)的執(zhí)行路徑。因此不同的進(jìn)程處于內(nèi)核態(tài)時,都需要有自己私有的進(jìn)程內(nèi)核棧 (process kernel stack)。

當(dāng)進(jìn)程從用戶態(tài)切換到內(nèi)核態(tài)時,所使用的棧會從用戶棧切換到內(nèi)核棧。

至于是如何切換的,關(guān)鍵詞是系統(tǒng)調(diào)用,這不是本文關(guān)注的重點,先放一邊,學(xué)習(xí)內(nèi)核要懂得恰當(dāng)?shù)臅r候忽略細(xì)節(jié)。

當(dāng)發(fā)生進(jìn)程切換時,也會切換到目標(biāo)進(jìn)程的內(nèi)核棧。

同上,關(guān)鍵詞是硬件上下文切換 (hardware context switch),忽略具體實現(xiàn)。

無論何時,只要進(jìn)程處于內(nèi)核態(tài),就會有內(nèi)核??梢允褂茫駝t系統(tǒng)就離崩潰不遠(yuǎn)了。

ARM 架構(gòu)的內(nèi)核棧和 task_struct 的關(guān)系如下:

 

Java+Linux,深入內(nèi)核源碼講解多線程之進(jìn)程

內(nèi)核棧的長度是 THREAD_SIZE,對于 ARM 架構(gòu),一般是 2 個頁框的大小,即 8KB。

內(nèi)核將一個較小的數(shù)據(jù)結(jié)構(gòu) thread_info 放在內(nèi)核棧的底部,它負(fù)責(zé)將內(nèi)核棧和 task_struct 串聯(lián)起來。thread_info 是平臺相關(guān)的,在 ARM 架構(gòu)中的定義如下:

 

  1. // thread_info.h (arch\arm\include\asm)  
  2. struct thread_info {  
  3. unsigned long flags; /* low level flags */  
  4. int preempt_count; /* 0 => preemptable, <0 => bug */  
  5. mm_segment_t addr_limit; /* address limit */  
  6. struct task_struct *task; /* main task structure */  
  7. [...]  
  8. struct cpu_context_save cpu_context; /* cpu context */  
  9. [...]  
  10. }; 

thread_info 保存了一個進(jìn)程能被調(diào)度執(zhí)行的最底層信息(low level task data),例如struct cpu_context_save cpu_context 會在進(jìn)程切換時用來保存/恢復(fù)寄存器上下文。

內(nèi)核通過內(nèi)核棧的棧指針可以快速地拿到 thread_info:

 

  1. // thread_info.h (include\linux)  
  2. static inline struct thread_info *current_thread_info(void)  
  3.  
  4. // current_stack_pointer 是當(dāng)前進(jìn)程內(nèi)核棧的棧指針  
  5. return (struct thread_info *)  
  6. (current_stack_pointer & ~(THREAD_SIZE - 1));  
  7.  
  8. 然后通過 thread_info 找到 task_struct:  
  9. // current.h (include\asm-generic)  
  10. #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)程時,內(nèi)核會新建一個 task_struct。

進(jìn)程的創(chuàng)建是個復(fù)雜的工作,可以延伸出無數(shù)的細(xì)節(jié)。這里我們只是簡單地了解一下 task_struct 的分配和部分初始化的流程。

fork() 在內(nèi)核里的核心流程:

 

Java+Linux,深入內(nèi)核源碼講解多線程之進(jìn)程

dup_task_struct() 做了什么?

 

Java+Linux,深入內(nèi)核源碼講解多線程之進(jìn)程

至于設(shè)置內(nèi)核棧里做了什么,涉及到了進(jìn)程的創(chuàng)建與切換,不在本文的關(guān)注范圍內(nèi),以后再研究了。

3. 實驗:打印 task_struct / thread_info / kernel mode stack

實驗?zāi)康模?/strong>

  • 梳理 task_struct / thread_info / kernel mode stack 的關(guān)系。

實驗代碼:

 

  1. 實驗代碼: 
  2. #include <linux/init.h> 
  3. #include <linux/module.h> 
  4. #include <linux/sched.h> 
  5.  
  6. static void print_task_info(struct task_struct *task) 
  7.     printk(KERN_NOTICE "%10s %5d task_struct (%p) / stack(%p~%p) / thread_info->task (%p)"
  8.         task->comm, 
  9.         task->pid, 
  10.         task, 
  11.         task->stack, 
  12.         ((unsigned long *)task->stack) + THREAD_SIZE, 
  13.         task_thread_info(task)->task); 
  14.  
  15. static int __init task_init(void) 
  16.     struct task_struct *task = current
  17.  
  18.     printk(KERN_INFO "task module init\n"); 
  19.  
  20.     print_task_info(task); 
  21.     do { 
  22.         task = task->parent; 
  23.         print_task_info(task); 
  24.     } while (task->pid != 0); 
  25.  
  26.     return 0; 
  27. module_init(task_init); 
  28.  
  29. static void __exit task_exit(void) 
  30.     printk(KERN_INFO "task module exit\n "); 
  31. module_exit(task_exit); 

運行效果:

 

  1. task module init 
  2.     insmod  3123 task_struct (edb42580) / stack(ed46c000~ed474000) / thread_info->task (edb42580) 
  3.       bash  2393 task_struct (eda13e80) / stack(c9dda000~c9de2000) / thread_info->task (eda13e80) 
  4.       sshd  2255 task_struct (ee5c9f40) / stack(c9d2e000~c9d36000) / thread_info->task (ee5c9f40) 
  5.       sshd   543 task_struct (ef15f080) / stack(ee554000~ee55c000) / thread_info->task (ef15f080) 
  6.    systemd     1 task_struct (ef058000) / stack(ef04c000~ef054000) / thread_info->task (ef058000) 

在程序里,我們通過 task_struct 找到 stack,然后通過 stack 找到 thread_info,最后又通過 thread_info->task 找到 task_struct。

到這里,不知道你對進(jìn)程的概念是不是有了一個清晰的理解

但是上面是通過Linux進(jìn)行了線程的展示,在日常的工作中,代碼的實現(xiàn)和編寫我們還是以Java為主,那我們來看一下Java進(jìn)程

1.Java進(jìn)程的創(chuàng)建

Java提供了兩種方法用來啟動進(jìn)程或其它程序:

  • 使用Runtime的exec()方法
  • 使用ProcessBuilder的start()方法

1.1 ProcessBuilder

ProcessBuilder類是J2SE 1.5在java.lang中新添加的一個新類,此類用于創(chuàng)建操作系統(tǒng)進(jìn)程,它提供一種啟動和管理進(jìn)程(也就是應(yīng)用程序)的方法。在J2SE 1.5之前,都是由Process類處來實現(xiàn)進(jìn)程的控制管理。

每個 ProcessBuilder 實例管理一個進(jìn)程屬性集。start() 方法利用這些屬性創(chuàng)建一個新的 Process 實例。start() 方法可以從同一實例重復(fù)調(diào)用,以利用相同的或相關(guān)的屬性創(chuàng)建新的子進(jìn)程。

每個進(jìn)程生成器管理這些進(jìn)程屬性:

  • 命令 是一個字符串列表,它表示要調(diào)用的外部程序文件及其參數(shù)(如果有)。在此,表示有效的操作系統(tǒng)命令的字符串列表是依賴于系統(tǒng)的。例如,每一個總體變量,通常都要成為此列表中的元素,但有一些操作系統(tǒng),希望程序能自己標(biāo)記命令行字符串——在這種系統(tǒng)中,Java 實現(xiàn)可能需要命令確切地包含這兩個元素。
  • 環(huán)境 是從變量 到值 的依賴于系統(tǒng)的映射。初始值是當(dāng)前進(jìn)程環(huán)境的一個副本(請參閱 System.getenv())。
  • 工作目錄。默認(rèn)值是當(dāng)前進(jìn)程的當(dāng)前工作目錄,通常根據(jù)系統(tǒng)屬性 user.dir 來命名。
  • redirectErrorStream 屬性。最初,此屬性為 false,意思是子進(jìn)程的標(biāo)準(zhǔn)輸出和錯誤輸出被發(fā)送給兩個獨立的流,這些流可以通過 Process.getInputStream() 和 Process.getErrorStream() 方法來訪問。如果將值設(shè)置為 true,標(biāo)準(zhǔn)錯誤將與標(biāo)準(zhǔn)輸出合并。這使得關(guān)聯(lián)錯誤消息和相應(yīng)的輸出變得更容易。在此情況下,合并的數(shù)據(jù)可從 Process.getInputStream() 返回的流讀取,而從 Process.getErrorStream() 返回的流讀取將直接到達(dá)文件尾。

修改進(jìn)程構(gòu)建器的屬性將影響后續(xù)由該對象的 start() 方法啟動的進(jìn)程,但從不會影響以前啟動的進(jìn)程或 Java 自身的進(jìn)程。大多數(shù)錯誤檢查由 start() 方法執(zhí)行??梢孕薷膶ο蟮臓顟B(tài),但這樣 start() 將會失敗。例如,將命令屬性設(shè)置為一個空列表將不會拋出異常,除非包含了 start()。

注意,此類不是同步的。如果多個線程同時訪問一個 ProcessBuilder,而其中至少一個線程從結(jié)構(gòu)上修改了其中一個屬性,它必須 保持外部同步。

構(gòu)造方法摘要

 

  1. ProcessBuilder(List command) 
  2.  
  3. 利用指定的操作系統(tǒng)程序和參數(shù)構(gòu)造一個進(jìn)程生成器。 
  4.  
  5. ProcessBuilder(String... command) 
  6.  
  7. 利用指定的操作系統(tǒng)程序和參數(shù)構(gòu)造一個進(jìn)程生成器。 

方法摘要

 

  1. List command() 
  2.  
  3. 返回此進(jìn)程生成器的操作系統(tǒng)程序和參數(shù)。 
  4.  
  5. ProcessBuilder command(List command) 
  6.  
  7. 設(shè)置此進(jìn)程生成器的操作系統(tǒng)程序和參數(shù)。 
  8.  
  9. ProcessBuilder command(String... command) 
  10.  
  11. 設(shè)置此進(jìn)程生成器的操作系統(tǒng)程序和參數(shù)。 
  12.  
  13. File directory() 
  14.  
  15. 返回此進(jìn)程生成器的工作目錄。 
  16.  
  17. ProcessBuilder directory(File directory) 
  18.  
  19. 設(shè)置此進(jìn)程生成器的工作目錄。 
  20.  
  21. Map environment() 
  22.  
  23. 返回此進(jìn)程生成器環(huán)境的字符串映射視圖。 
  24.  
  25. boolean redirectErrorStream() 
  26.  
  27. 通知進(jìn)程生成器是否合并標(biāo)準(zhǔn)錯誤和標(biāo)準(zhǔn)輸出。 
  28.  
  29. ProcessBuilder redirectErrorStream(boolean redirectErrorStream) 
  30.  
  31. 設(shè)置此進(jìn)程生成器的 redirectErrorStream 屬性。 
  32.  
  33. Process start() 
  34.  
  35. 使用此進(jìn)程生成器的屬性啟動一個新進(jìn)程。 

1.2 Runtime

每個 Java 應(yīng)用程序都有一個 Runtime 類實例,使應(yīng)用程序能夠與其運行的環(huán)境相連接??梢酝ㄟ^ getRuntime 方法獲取當(dāng)前運行時。

應(yīng)用程序不能創(chuàng)建自己的 Runtime 類實例。但可以通過 getRuntime 方法獲取當(dāng)前Runtime運行時對象的引用。一旦得到了一個當(dāng)前的Runtime對象的引用,就可以調(diào)用Runtime對象的方法去控制Java虛擬機(jī)的狀態(tài)和行為。

Java代碼 收藏代碼

 

  1. void addShutdownHook(Thread hook)  
  2. 注冊新的虛擬機(jī)來關(guān)閉掛鉤。 
  3.  
  4. int availableProcessors()  
  5. 向 Java 虛擬機(jī)返回可用處理器的數(shù)目。 
  6.  
  7. Process exec(String command)  
  8. 在單獨的進(jìn)程中執(zhí)行指定的字符串命令。 
  9.  
  10. Process exec(String[] cmdarray)  
  11. 在單獨的進(jìn)程中執(zhí)行指定命令和變量。 
  12.  
  13. Process exec(String[] cmdarray, String[] envp)  
  14. 在指定環(huán)境的獨立進(jìn)程中執(zhí)行指定命令和變量。 
  15.  
  16. Process exec(String[] cmdarray, String[] envp, File dir)  
  17. 在指定環(huán)境和工作目錄的獨立進(jìn)程中執(zhí)行指定的命令和變量。 
  18.  
  19. Process exec(String command, String[] envp)  
  20. 在指定環(huán)境的單獨進(jìn)程中執(zhí)行指定的字符串命令。 
  21.  
  22. Process exec(String command, String[] envp, File dir)  
  23. 在有指定環(huán)境和工作目錄的獨立進(jìn)程中執(zhí)行指定的字符串命令。 
  24.  
  25. void exit(int status)  
  26. 通過啟動虛擬機(jī)的關(guān)閉序列,終止當(dāng)前正在運行的 Java 虛擬機(jī)。 
  27.  
  28. long freeMemory()  
  29. 返回 Java 虛擬機(jī)中的空閑內(nèi)存量。 
  30.  
  31. void gc()  
  32. 運行垃圾回收器。 
  33.  
  34. InputStream getLocalizedInputStream(InputStream in 
  35. 已過時。 從 JDK 1.1 開始,將本地編碼字節(jié)流轉(zhuǎn)換為 Unicode 字符流的首選方法是使用 InputStreamReader 和 BufferedReader 類。 
  36.  
  37. OutputStream getLocalizedOutputStream(OutputStream out 
  38. 已過時。 從 JDK 1.1 開始,將 Unicode 字符流轉(zhuǎn)換為本地編碼字節(jié)流的首選方法是使用 OutputStreamWriter、BufferedWriter 和 PrintWriter 類。 
  39.  
  40. static Runtime getRuntime()  
  41. 返回與當(dāng)前 Java 應(yīng)用程序相關(guān)的運行時對象。 
  42.  
  43. void halt(int status)  
  44. 強(qiáng)行終止目前正在運行的 Java 虛擬機(jī)。 
  45.  
  46. void load(String filename)  
  47. 加載作為動態(tài)庫的指定文件名。 
  48.  
  49. void loadLibrary(String libname)  
  50. 加載具有指定庫名的動態(tài)庫。 
  51.  
  52. long maxMemory()  
  53. 返回 Java 虛擬機(jī)試圖使用的最大內(nèi)存量。 
  54.  
  55. boolean removeShutdownHook(Thread hook)  
  56. 取消注冊某個先前已注冊的虛擬機(jī)關(guān)閉掛鉤。 
  57.  
  58. void runFinalization()  
  59. 運行掛起 finalization 的所有對象的終止方法。 
  60.  
  61. static void runFinalizersOnExit(boolean value)  
  62. 已過時。 此方法本身具有不安全性。它可能對正在使用的對象調(diào)用終結(jié)方法,而其他線程正在操作這些對象,從而導(dǎo)致不正確的行為或死鎖。 
  63.  
  64. long totalMemory()  
  65. 返回 Java 虛擬機(jī)中的內(nèi)存總量。 
  66.  
  67. void traceInstructions(boolean on 
  68. 啟用/禁用指令跟蹤。 
  69.  
  70. void traceMethodCalls(boolean on 
  71. 啟用/禁用方法調(diào)用跟蹤。 

1.3 Process

不管通過哪種方法啟動進(jìn)程后,都會返回一個Process類的實例代表啟動的進(jìn)程,該實例可用來控制進(jìn)程并獲得相關(guān)信息。Process 類提供了執(zhí)行從進(jìn)程輸入、執(zhí)行輸出到進(jìn)程、等待進(jìn)程完成、檢查進(jìn)程的退出狀態(tài)以及銷毀(殺掉)進(jìn)程的方法:

 

  1. void destroy()  
  2. 殺掉子進(jìn)程。 
  3.  
  4. 一般情況下,該方法并不能殺掉已經(jīng)啟動的進(jìn)程,不用為好。  
  5. int exitValue() 
  6.  
  7. 返回子進(jìn)程的出口值。 
  8.  
  9. 只有啟動的進(jìn)程執(zhí)行完成、或者由于異常退出后,exitValue()方法才會有正常的返回值,否則拋出異常。  
  10. InputStream getErrorStream() 
  11.  
  12. 獲取子進(jìn)程的錯誤流。 
  13.  
  14. 如果錯誤輸出被重定向,則不能從該流中讀取錯誤輸出。  
  15. InputStream getInputStream() 
  16.  
  17. 獲取子進(jìn)程的輸入流。 
  18.  
  19. 可以從該流中讀取進(jìn)程的標(biāo)準(zhǔn)輸出。  
  20. OutputStream getOutputStream() 
  21.  
  22. 獲取子進(jìn)程的輸出流。 
  23.  
  24. 寫入到該流中的數(shù)據(jù)作為進(jìn)程的標(biāo)準(zhǔn)輸入。  
  25. int waitFor() 
  26.  
  27. 導(dǎo)致當(dāng)前線程等待,如有必要,一直要等到由該 Process 對象表示的進(jìn)程已經(jīng)終止。 

2.多進(jìn)程編程實例

一般我們在java中運行其它類中的方法時,無論是靜態(tài)調(diào)用,還是動態(tài)調(diào)用,都是在當(dāng)前的進(jìn)程中執(zhí)行的,也就是說,只有一個java虛擬機(jī)實例在運行。而有的時候,我們需要通過java代碼啟動多個java子進(jìn)程。這樣做雖然占用了一些系統(tǒng)資源,但會使程序更加穩(wěn)定,因為新啟動的程序是在不同的虛擬機(jī)進(jìn)程中運行的,如果有一個進(jìn)程發(fā)生異常,并不影響其它的子進(jìn)程。

在Java中我們可以使用兩種方法來實現(xiàn)這種要求。最簡單的方法就是通過Runtime中的exec方法執(zhí)行java classname。如果執(zhí)行成功,這個方法返回一個Process對象,如果執(zhí)行失敗,將拋出一個IOException錯誤。下面讓我們來看一個簡單的例子。

 

  1. // Test1.java文件 
  2. import java.io.*; 
  3. public class Test 
  4.  public static void main(String[] args) 
  5.  { 
  6.   FileOutputStream fOut = new FileOutputStream("c:\\Test1.txt"); 
  7.   fOut.close(); 
  8.   System.out.println("被調(diào)用成功!"); 
  9.  } 
  10.   
  11. // Test_Exec.java 
  12. public class Test_Exec 
  13.  public static void main(String[] args) 
  14.  { 
  15.   Runtime run = Runtime.getRuntime(); 
  16.   Process p = run.exec("java test1"); 
  17.  } 

通過java Test_Exec運行程序后,發(fā)現(xiàn)在C盤多了個Test1.txt文件,但在控制臺中并未出現(xiàn)"被調(diào)用成功!"的輸出信息。因此可以斷定,Test已經(jīng)被執(zhí)行成功,但因為某種原因,Test的輸出信息未在Test_Exec的控制臺中輸出。這個原因也很簡單,因為使用exec建立的是Test_Exec的子進(jìn)程,這個子進(jìn)程并沒有自己的控制臺,因此,它并不會輸出任何信息。

如果要輸出子進(jìn)程的輸出信息,可以通過Process中的getInputStream得到子進(jìn)程的輸出流(在子進(jìn)程中輸出,在父進(jìn)程中就是輸入),然后將子進(jìn)程中的輸出流從父進(jìn)程的控制臺輸出。具體的實現(xiàn)代碼如下如示:

 

  1. // Test_Exec_Out.java 
  2. import java.io.*; 
  3. public class Test_Exec_Out 
  4.  public static void main(String[] args) 
  5.  { 
  6.   Runtime run = Runtime.getRuntime(); 
  7.   Process p = run.exec("java test1"); 
  8.   BufferedInputStream in = new BufferedInputStream(p.getInputStream()); 
  9.   BufferedReader br = new BufferedReader(new InputStreamReader(in)); 
  10.   String s; 
  11.   while ((s = br.readLine()) != null
  12.    System.out.println(s); 
  13.  } 

從上面的代碼可以看出,在Test_Exec_Out.java中通過按行讀取子進(jìn)程的輸出信息,然后在Test_Exec_Out中按每行進(jìn)行輸出。 上面討論的是如何得到子進(jìn)程的輸出信息。那么,除了輸出信息,還有輸入信息。既然子進(jìn)程沒有自己的控制臺,那么輸入信息也得由父進(jìn)程提供。我們可以通過Process的getOutputStream方法來為子進(jìn)程提供輸入信息(即由父進(jìn)程向子進(jìn)程輸入信息,而不是由控制臺輸入信息)。我們可以看看如下的代碼:

 

  1. // Test2.java文件 
  2. import java.io.*; 
  3. public class Test 
  4.  public static void main(String[] args) 
  5.  { 
  6.   BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
  7.   System.out.println("由父進(jìn)程輸入的信息:" + br.readLine()); 
  8.  } 
  9.   
  10. // Test_Exec_In.java 
  11. import java.io.*; 
  12. public class Test_Exec_In 
  13.  public static void main(String[] args) 
  14.  { 
  15.   Runtime run = Runtime.getRuntime(); 
  16.   Process p = run.exec("java test2"); 
  17.   BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(p.getOutputStream())); 
  18.   bw.write("向子進(jìn)程輸出信息"); 
  19.   bw.flush(); 
  20.   bw.close(); // 必須得關(guān)閉流,否則無法向子進(jìn)程中輸入信息 
  21.   // System.in.read(); 
  22.  } 

從以上代碼可以看出,Test1得到由Test_Exec_In發(fā)過來的信息,并將其輸出。當(dāng)你不加bw.flash()和bw.close()時,信息將無法到達(dá)子進(jìn)程,也就是說子進(jìn)程進(jìn)入阻塞狀態(tài),但由于父進(jìn)程已經(jīng)退出了,因此,子進(jìn)程也跟著退出了。如果要證明這一點,可以在最后加上System.in.read(),然后通過任務(wù)管理器(在windows下)查看java進(jìn)程,你會發(fā)現(xiàn)如果加上bw.flush()和bw.close(),只有一個java進(jìn)程存在,如果去掉它們,就有兩個java進(jìn)程存在。這是因為,如果將信息傳給Test2,在得到信息后,Test2就退出了。在這里有一點需要說明一下,exec的執(zhí)行是異步的,并不會因為執(zhí)行的某個程序阻塞而停止執(zhí)行下面的代碼。因此,可以在運行test2后,仍可以執(zhí)行下面的代碼。

exec方法經(jīng)過了多次的重載。上面使用的只是它的一種重載。它還可以將命令和參數(shù)分開,如exec("java.test2")可以寫成exec("java", "test2")。exec還可以通過指定的環(huán)境變量運行不同配置的java虛擬機(jī)。

除了使用Runtime的exec方法建立子進(jìn)程外,還可以通過ProcessBuilder建立子進(jìn)程。ProcessBuilder的使用方法如下:

 

  1. // Test_Exec_Out.java 
  2. import java.io.*; 
  3. public class Test_Exec_Out 
  4.  public static void main(String[] args) 
  5.  { 
  6.   ProcessBuilder pb = new ProcessBuilder("java""test1"); 
  7.   Process p = pb.start(); 
  8.   … … 
  9.  } 

在建立子進(jìn)程上,ProcessBuilder和Runtime類似,不同的ProcessBuilder使用start()方法啟動子進(jìn)程,而Runtime使用exec方法啟動子進(jìn)程。得到Process后,它們的操作就完全一樣的。

ProcessBuilder和Runtime一樣,也可設(shè)置可執(zhí)行文件的環(huán)境信息、工作目錄等。下面的例子描述了如何使用ProcessBuilder設(shè)置這些信息。

 

  1. ProcessBuilder pb = new ProcessBuilder("Command""arg2""arg2"'''); 
  2. // 設(shè)置環(huán)境變量 
  3. Map<String, String> env = pb.environment(); 
  4. env.put("key1""value1"); 
  5. env.remove("key2"); 
  6. env.put("key2", env.get("key1") + "_test"); 
  7. pb.directory("..\abcd"); // 設(shè)置工作目錄 
  8. Process p = pb.start(); // 建立子進(jìn)程 

 

責(zé)任編輯:未麗燕 來源: 今日頭條
相關(guān)推薦

2021-03-06 22:41:06

內(nèi)核源碼CAS

2009-12-11 09:42:54

Linux內(nèi)核源碼進(jìn)程調(diào)度

2009-12-11 09:47:23

Linux內(nèi)核源碼進(jìn)程調(diào)度

2021-12-26 18:22:30

Java線程多線程

2010-01-21 11:25:44

linux多線程線程資源

2009-12-24 17:06:35

編譯Fedora內(nèi)核

2021-12-28 09:10:55

Java線程狀態(tài)

2021-12-14 08:28:08

Java多線程線程

2013-12-02 17:33:20

Linux進(jìn)程多線程

2023-11-24 11:24:16

Linux系統(tǒng)

2021-07-26 07:47:36

數(shù)據(jù)庫

2010-03-15 19:37:00

Java多線程同步

2010-03-16 17:16:38

Java多線程

2021-07-20 08:02:41

Linux進(jìn)程睡眠

2017-03-19 16:57:59

LinuxAMD處理器

2009-11-18 15:14:06

PHP線程

2021-06-11 11:28:22

多線程fork單線程

2016-03-14 16:35:40

IT專家網(wǎng)

2022-05-26 08:31:41

線程Java線程與進(jìn)程

2020-04-29 15:10:16

Linux命令進(jìn)程
點贊
收藏

51CTO技術(shù)棧公眾號