中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中的進程與線程有什么關系

發布時間:2020-11-20 15:31:11 來源:億速云 閱讀:137 作者:Leah 欄目:編程語言

本篇文章給大家分享的是有關Java中的進程與線程有什么關系,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

進程與線程,本質意義上說, 是操作系統的調度單位,可以看成是一種操作系統 “資源” 。Java 作為與平臺無關的編程語言,必然會對底層(操作系統)提供的功能進行進一步的封裝,以平臺無關的編程接口供程序員使用,進程與線程作為操作系統核心概念的一部分無疑亦是如此。在 Java 語言中,對進程和線程的封裝,分別提供了 Process 和 Thread 相關的一些類。本文首先簡單的介紹如何使用這些類來創建進程和線程,然后著重介紹這些類是如何和操作系統本地進程線程相對應的,給出了 Java 虛擬機對于這些封裝類的概要性的實現;同時由于 Java 的封裝也隱藏了底層的一些概念和可操作性,本文還對 Java 進程線程和本地進程線程做了一些簡單的比較,列出了使用 Java 進程、線程的一些限制和需要注意的問題。

Java 進程的建立方法

在 JDK 中,與進程有直接關系的類為 Java.lang.Process,它是一個抽象類。在 JDK 中也提供了一個實現該抽象類的 ProcessImpl 類,如果用戶創建了一個進程,那么肯定會伴隨著一個新的 ProcessImpl 實例。同時和進程創建密切相關的還有 ProcessBuilder,它是在 JDK1.5 中才開始出現的,相對于 Process 類來說,提供了便捷的配置新建進程的環境,目錄以及是否合并錯誤流和輸出流的方式。

Java.lang.Runtime.exec 方法和 Java.lang.ProcessBuilder.start 方法都可以創建一個本地的進程,然后返回代表這個進程的 Java.lang.Process 引用。

Runtime.exec 方法建立一個本地進程

該方法在 JDK1.5 中,可以接受 6 種不同形式的參數傳入。

 Process exec(String command) 
 Process exec(String [] cmdarray) 
 Process exec(String [] cmdarrag, String [] envp) 
 Process exec(String [] cmdarrag, String [] envp, File dir) 
 Process exec(String cmd, String [] envp) 
 Process exec(String command, String [] envp, File dir)

他們主要的不同在于傳入命令參數的形式,提供的環境變量以及定義執行目錄。

ProcessBuilder.start 方法來建立一個本地的進程

如果希望在新創建的進程中使用當前的目錄和環境變量,則不需要任何配置,直接將命令行和參數傳入 ProcessBuilder 中,然后調用 start 方法,就可以獲得進程的引用。

Process p = new ProcessBuilder("command", "param").start();

也可以先配置環境變量和工作目錄,然后創建進程。

 ProcessBuilder pb = new ProcessBuilder("command", "param1", "param2"); 
 Map<String, String> env = pb.environment(); 
 env.put("VAR", "Value"); 
 pb.directory("Dir"); 
 Process p = pb.start();

可以預先配置 ProcessBuilder 的屬性是通過 ProcessBuilder 創建進程的最大優點。而且可以在后面的使用中隨著需要去改變代碼中 pb 變量的屬性。如果后續代碼修改了其屬性,那么會影響到修改后用 start 方法創建的進程,對修改之前創建的進程實例沒有影響。

JVM 對進程的實現

在 JDK 的代碼中,只提供了 ProcessImpl 類來實現 Process 抽象類。其中引用了 native 的 create, close, waitfor, destory 和 exitValue 方法。在 Java 中,native 方法是依賴于操作系統平臺的本地方法,它的實現是用 C/C++ 等類似的底層語言實現。我們可以在 JVM 的源代碼中找到對應的本地方法,然后對其進行分析。JVM 對進程的實現相對比較簡單,以 Windows 下的 JVM 為例。在 JVM 中,將 Java 中調用方法時的傳入的參數傳遞給操作系統對應的方法來實現相應的功能。如表 1

表 1. JDK 中 native 方法與 Windows API 的對應關系

Java中的進程與線程有什么關系

以 create 方法為例,我們看一下它是如何和系統 API 進行連接的。

在 ProcessImple 類中,存在 native 的 create 方法,其參數如下:

 private native long create(String cmdstr, String envblock, 
 String dir, boolean redirectErrorStream, FileDescriptor in_fd, 
 FileDescriptor out_fd, FileDescriptor err_fd) throws IOException;

在 JVM 中對應的本地方法如代碼清單 1 所示 。

清單 1

JNIEXPORT jlong JNICALL 
 Java_Java_lang_ProcessImpl_create(JNIEnv *env, jobject process, 
 jstring cmd, 
 jstring envBlock, 
 jstring dir, 
 jboolean redirectErrorStream, 
 jobject in_fd, 
 jobject out_fd, 
 jobject err_fd) 
 { 
   /* 設置內部變量值 */ 
 ……
   /* 建立輸入、輸出以及錯誤流管道 */ 
 if (!(CreatePipe(&inRead, &inWrite, &sa, PIPE_SIZE) && 
  CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE) && 
  CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE))) { 
  throwIOException(env, "CreatePipe failed"); 
  goto Catch; 
  } 
   /* 進行參數格式的轉換 */ 
  pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL); 
  ……
   /* 調用系統提供的方法,建立一個 Windows 的進程 */ 
  ret = CreateProcess( 
  0,      /* executable name */ 
  pcmd,    /* command line */ 
  0,      /* process security attribute */ 
  0,      /* thread security attribute */ 
  TRUE,    /* inherits system handles */ 
  processFlag, /* selected based on exe type */ 
  penvBlock,  /* environment block */ 
  pdir,    /* change to the new current directory */ 
  &si,   /* (in) startup information */ 
  &pi);   /* (out) process information */ 
 …
   /* 拿到新進程的句柄 */ 
  ret = (jlong)pi.hProcess; 
 …
   /* 最后返回該句柄 */ 
  return ret; 
 }

可以看到在創建一個進程的時候,調用 Windows 提供的 CreatePipe 方法建立輸入,輸出和錯誤管道,同時將用戶通過 Java 傳入的參數轉換為操作系統可以識別的 C 語言的格式,然后調用 Windows 提供的創建系統進程的方式,創建一個進程,同時在 JAVA 虛擬機中保存了這個進程對應的句柄,然后返回給了 ProcessImpl 類,但是該類將返回句柄進行了隱藏。也正是 Java 跨平臺的特性體現,JVM 盡可能的將和操作系統相關的實現細節進行了封裝,并隱藏了起來。
同樣,在用戶調用 close、waitfor、destory 以及 exitValue 方法以后, JVM 會首先取得之前保存的該進程在操作系統中的句柄,然后通過調用操作系統提供的接口對該進程進行操作。通過這種方式來實現對進程的操作。
在其它平臺下也是用類似的方式實現的,不同的是調用的對應平臺的 API 會有所不同。

Java 進程與操作系統進程

通過上面對 Java 進程的分析,其實它在實現上就是創建了操作系統的一個進程,也就是每個 JVM 中創建的進程都對應了操作系統中的一個進程。但是,Java 為了給用戶更好的更方便的使用,向用戶屏蔽了一些與平臺相關的信息,這為用戶需要使用的時候,帶來了些許不便。

在使用 C/C++ 創建系統進程的時候,是可以獲得進程的 PID 值的,可以直接通過該 PID 去操作相應進程。但是在 JAVA 中,用戶只能通過實例的引用去進行操作,當該引用丟失或者無法取得的時候,就無法了解任何該進程的信息。

當然,Java 進程在使用的時候還有些要注意的事情:

1. Java 提供的輸入輸出的管道容量是十分有限的,如果不及時讀取會導致進程掛起甚至引起死鎖。

2. 當創建進程去執行 Windows 下的系統命令時,如:dir、copy 等。需要運行 windows 的命令解釋器,command.exe/cmd.exe,這依賴于 windows 的版本,這樣才可以運行系統的命令。

3. 對于 Shell 中的管道 ‘ | '命令,各平臺下的重定向命令符 ‘ > ',都無法通過命令參數直接傳入進行實現,而需要在 Java 代碼中做一些處理,如定義新的流來存儲標準輸出,等等問題。

總之,Java 中對操作系統的進程進行了封裝,屏蔽了操作系統進程相關的信息。同時,在使用 Java 提供創建進程運行本地命令的時候,需要小心使用。

一般而言,使用進程是為了執行某項任務,而現代操作系統對于執行任務的計算資源的配置調度一般是以線程為對象(早期的類 Unix 系統因為不支持線程,所以進程也是調度單位,但那是比較輕量級的進程,在此不做深入討論)。創建一個進程,操作系統實際上還是會為此創建相應的線程以運行一系列指令。特別地,當一個任務比較龐大復雜,可能需要創建多個線程以實現邏輯上并發執行的時候,線程的作用更為明顯。因而我們有必要深入了解 Java 中的線程,以避免可能出現的問題。本文下面的內容即是呈現 Java 線程的創建方式以及它與操作系統線程的聯系與區別。

Java 創建線程的方法

實際上,創建線程最重要的是提供線程函數(回調函數),該函數作為新創建線程的入口函數,實現自己想要的功能。Java 提供了兩種方法來創建一個線程:

1. 繼承 Thread 類

 class MyThread extends Thread{ 
 public void run() { 
  System.out.println("My thread is started."); 
 } 
 }

實現該繼承類的 run 方法,然后就可以創建這個子類的對象,調用 start 方法即可創建一個新的線程:

 MyThread myThread = new MyThread(); 
 myThread.start();

2. 實現 Runnable 接口

 class MyRunnable implements Runnable{ 
 public void run() { 
   System.out.println("My runnable is invoked."); 
 } 
 }

實現 Runnable 接口的類的對象可以作為一個參數傳遞到創建的 Thread 對象中,同樣調用 Thread#start 方法就可以在一個新的線程中運行 run 方法中的代碼了。 

 Thread myThread = new Thread( new MyRunnable()); 
 myThread.start();

可以看到,不管是用哪種方法,實際上都是要實現一個 run 方法的。 該方法本質是上一個回調方法。由 start 方法新創建的線程會調用這個方法從而執行需要的代碼。 從后面可以看到,run 方法并不是真正的線程函數,只是被線程函數調用的一個 Java 方法而已,和其他的 Java 方法沒有什么本質的不同。

Java 線程的實現

從概念上來說,一個 Java 線程的創建根本上就對應了一個本地線程(native thread)的創建,兩者是一一對應的。 問題是,本地線程執行的應該是本地代碼,而 Java 線程提供的線程函數是 Java 方法,編譯出的是 Java 字節碼,所以可以想象的是, Java 線程其實提供了一個統一的線程函數,該線程函數通過 Java 虛擬機調用 Java 線程方法 , 這是通過 Java 本地方法調用來實現的。

以下是 Thread#start 方法的示例:

 public synchronized void start() { 
   …
   start0(); 
   …
 }

可以看到它實際上調用了本地方法 start0, 該方法的聲明如下:

private native void start0();

Thread 類有個 registerNatives 本地方法,該方法主要的作用就是注冊一些本地方法供 Thread 類使用,如 start0(),stop0() 等等,可以說,所有操作本地線程的本地方法都是由它注冊的 . 這個方法放在一個 static 語句塊中,這就表明,當該類被加載到 JVM 中的時候,它就會被調用,進而注冊相應的本地方法。

 private static native void registerNatives(); 
 static{ 
    registerNatives(); 
 }

本地方法 registerNatives 是定義在 Thread.c 文件中的。Thread.c 是個很小的文件,定義了各個操作系統平臺都要用到的關于線程的公用數據和操作,如代碼清單 2 所示。

清單 2

JNIEXPORT void JNICALL 
 Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){ 
  (*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods)); 
 } 
 static JNINativeMethod methods[] = { 
  {"start0", "()V",(void *)&JVM_StartThread}, 
  {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, 
 {"isAlive","()Z",(void *)&JVM_IsThreadAlive}, 
 {"suspend0","()V",(void *)&JVM_SuspendThread}, 
 {"resume0","()V",(void *)&JVM_ResumeThread}, 
 {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, 
 {"yield", "()V",(void *)&JVM_Yield}, 
 {"sleep","(J)V",(void *)&JVM_Sleep}, 
 {"currentThread","()" THD,(void *)&JVM_CurrentThread}, 
 {"countStackFrames","()I",(void *)&JVM_CountStackFrames}, 
 {"interrupt0","()V",(void *)&JVM_Interrupt}, 
 {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, 
 {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, 
 {"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, 
 {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, 
 };

到此,可以容易的看出 Java 線程調用 start 的方法,實際上會調用到 JVM_StartThread 方法,那這個方法又是怎樣的邏輯呢。實際上,我們需要的是(或者說 Java 表現行為)該方法最終要調用 Java 線程的 run 方法,事實的確如此。 在 jvm.cpp 中,有如下代碼段:

 JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) 
 …
 native_thread = new JavaThread(&thread_entry, sz); 
 …

這里JVM_ENTRY是一個宏,用來定義JVM_StartThread 函數,可以看到函數內創建了真正的平臺相關的本地線程,其線程函數是 thread_entry,如清單 3 所示。

清單 3

 static void thread_entry(JavaThread* thread, TRAPS) { 
  HandleMark hm(THREAD); 
 Handle obj(THREAD, thread->threadObj()); 
 JavaValue result(T_VOID); 
 JavaCalls::call_virtual(&result,obj, 
 KlassHandle(THREAD,SystemDictionary::Thread_klass()), 
 vmSymbolHandles::run_method_name(), 
 vmSymbolHandles::void_method_signature(),THREAD); 
 }

可以看到調用了 vmSymbolHandles::run_method_name 方法,這是在 vmSymbols.hpp 用宏定義的:

class vmSymbolHandles: AllStatic { 
 …
 template(run_method_name,"run") 
 …
 }

至于 run_method_name 是如何聲明定義的,因為涉及到很繁瑣的代碼細節,本文不做贅述。感興趣的讀者可以自行查看 JVM 的源代碼。

圖 1. Java 線程創建調用關系圖

Java中的進程與線程有什么關系 

綜上所述,Java 線程的創建調用過程如 圖 1 所示,首先 , Java 線程的 start 方法會創建一個本地線程(通過調用 JVM_StartThread),該線程的線程函數是定義在 jvm.cpp 中的 thread_entry,由其再進一步調用 run 方法。可以看到 Java 線程的 run 方法和普通方法其實沒有本質區別,直接調用 run 方法不會報錯,但是卻是在當前線程執行,而不會創建一個新的線程。

Java 線程與操作系統線程

從上我們知道,Java 線程是建立在系統本地線程之上的,是另一層封裝,其面向 Java 開發者提供的接口存在以下的局限性:

線程返回值

Java 沒有提供方法來獲取線程的退出返回值。實際上,線程可以有退出返回值,它一般被操作系統存儲在線程控制結構中 (TCB),調用者可以通過檢測該值來確定線程是正常退出還是異常終止。

線程的同步

Java 提供方法 Thread#Join()來等待一個線程結束,一般情況這就足夠了,但一種可能的情況是,需要等待在多個線程上(比如任意一個線程結束或者所有線程結束才會返回),循環調用每個線程的 Join 方法是不可行的,這可能導致很奇怪的同步問題。

線程的 ID

Java 提供的方法 Thread#getID()返回的是一個簡單的計數 ID,其實和操作系統線程的 ID 沒有任何關系。

線程運行時間統計,Java 沒有提供方法來獲取線程中某段代碼的運行時間的統計結果。雖然可以自行使用計時的方法來實現(獲取運行開始和結束的時間,然后相減 ),但由于存在多線程調度方法的原因,無法獲取線程實際使用的 CPU 運算時間,因而必然是不準確的。

以上就是Java中的進程與線程有什么關系,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

昌黎县| 若尔盖县| 和平县| 石狮市| 偃师市| 塔河县| 新闻| 望城县| 平定县| 丘北县| 镇赉县| 平凉市| 余姚市| 绵竹市| 水富县| 都兰县| 东台市| 平武县| 建始县| 彭州市| 屯昌县| 崇阳县| 文登市| 邵武市| 盱眙县| 西乌珠穆沁旗| 天柱县| 什邡市| 嘉鱼县| 讷河市| 福海县| 安多县| 建始县| 巴林左旗| 文化| 四会市| 五河县| 福贡县| 永吉县| 麻城市| 兴文县|