亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

簡述Java中進程與線程的關(guān)系_動力節(jié)點Java學(xué)院整理

 更新時間:2017年05月22日 14:13:37   投稿:mrr  
在 Java 語言中,對進程和線程的封裝,分別提供了 Process 和 Thread 相關(guān)的一些類。本文首先簡單的介紹如何使用這些類來創(chuàng)建進程和線程

概述

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

Java 進程的建立方法

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

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

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

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

 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)

他們主要的不同在于傳入命令參數(shù)的形式,提供的環(huán)境變量以及定義執(zhí)行目錄。

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

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

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

也可以先配置環(huán)境變量和工作目錄,然后創(chuàng)建進程。

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

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

JVM 對進程的實現(xiàn)

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

表 1. JDK 中 native 方法與 Windows API 的對應(yīng)關(guān)系

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

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

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

在 JVM 中對應(yīng)的本地方法如代碼清單 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) 
 { 
   /* 設(shè)置內(nèi)部變量值 */ 
 ……
   /* 建立輸入、輸出以及錯誤流管道 */ 
 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; 
  } 
   /* 進行參數(shù)格式的轉(zhuǎn)換 */ 
  pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL); 
  ……
   /* 調(diào)用系統(tǒng)提供的方法,建立一個 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; 
 }

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

Java 進程與操作系統(tǒng)進程

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

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

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

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

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

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

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

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

Java 創(chuàng)建線程的方法

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

1. 繼承 Thread 類

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

實現(xiàn)該繼承類的 run 方法,然后就可以創(chuàng)建這個子類的對象,調(diào)用 start 方法即可創(chuàng)建一個新的線程:

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

2. 實現(xiàn) Runnable 接口

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

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

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

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

Java 線程的實現(xiàn)

從概念上來說,一個 Java 線程的創(chuàng)建根本上就對應(yīng)了一個本地線程(native thread)的創(chuàng)建,兩者是一一對應(yīng)的。 問題是,本地線程執(zhí)行的應(yīng)該是本地代碼,而 Java 線程提供的線程函數(shù)是 Java 方法,編譯出的是 Java 字節(jié)碼,所以可以想象的是, Java 線程其實提供了一個統(tǒng)一的線程函數(shù),該線程函數(shù)通過 Java 虛擬機調(diào)用 Java 線程方法 , 這是通過 Java 本地方法調(diào)用來實現(xiàn)的。

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

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

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

private native void start0();

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

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

本地方法 registerNatives 是定義在 Thread.c 文件中的。Thread.c 是個很小的文件,定義了各個操作系統(tǒng)平臺都要用到的關(guān)于線程的公用數(shù)據(jù)和操作,如代碼清單 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 線程調(diào)用 start 的方法,實際上會調(diào)用到 JVM_StartThread 方法,那這個方法又是怎樣的邏輯呢。實際上,我們需要的是(或者說 Java 表現(xiàn)行為)該方法最終要調(diào)用 Java 線程的 run 方法,事實的確如此。 在 jvm.cpp 中,有如下代碼段:

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

這里JVM_ENTRY是一個宏,用來定義JVM_StartThread 函數(shù),可以看到函數(shù)內(nèi)創(chuàng)建了真正的平臺相關(guān)的本地線程,其線程函數(shù)是 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); 
 }

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

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

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

圖 1. Java 線程創(chuàng)建調(diào)用關(guān)系圖

 

綜上所述,Java 線程的創(chuàng)建調(diào)用過程如 圖 1 所示,首先 , Java 線程的 start 方法會創(chuàng)建一個本地線程(通過調(diào)用 JVM_StartThread),該線程的線程函數(shù)是定義在 jvm.cpp 中的 thread_entry,由其再進一步調(diào)用 run 方法??梢钥吹?Java 線程的 run 方法和普通方法其實沒有本質(zhì)區(qū)別,直接調(diào)用 run 方法不會報錯,但是卻是在當(dāng)前線程執(zhí)行,而不會創(chuàng)建一個新的線程。

Java 線程與操作系統(tǒng)線程

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

線程返回值

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

線程的同步

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

線程的 ID

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

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

總結(jié)

本文通過對 Java 進程和線程的分析,可以看出 Java 對這兩種操作系統(tǒng) “資源” 進行了封裝,使得開發(fā)人員只需關(guān)注如何使用這兩種 “資源” ,而不必過多的關(guān)心細節(jié)。這樣的封裝一方面降低了開發(fā)人員的工作復(fù)雜度,提高了工作效率;另一方面由于封裝屏蔽了操作系統(tǒng)本身的一些特性,因而在使用 Java 進程線程時有了某些限制,這是封裝不可避免的問題。語言的演化本就是決定需要什么不需要什么的過程,相信隨著 Java 的不斷發(fā)展,封裝的功能子集的必然越來越完善。

相關(guān)文章

  • java中深復(fù)制知識點詳解

    java中深復(fù)制知識點詳解

    在本篇文章里小編給大家整理了關(guān)于java中深復(fù)制知識點詳解內(nèi)容,有需要的朋友們可以學(xué)習(xí)下。
    2020-12-12
  • Java實現(xiàn)雙端鏈表LinkedList

    Java實現(xiàn)雙端鏈表LinkedList

    本文主要介紹了Java實現(xiàn)雙端鏈表LinkedList,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Java版的7種單例模式寫法示例

    Java版的7種單例模式寫法示例

    這篇文章主要給大家介紹了關(guān)于Java版的7種單例模式寫法,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • Hibernate映射解析之關(guān)聯(lián)映射詳解

    Hibernate映射解析之關(guān)聯(lián)映射詳解

    所謂關(guān)聯(lián)映射就是將關(guān)聯(lián)關(guān)系映射到數(shù)據(jù)庫里,在對象模型中就是一個或多個引用。下面這篇文章詳細的給大家介紹了Hibernate映射解析之關(guān)聯(lián)映射的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-02-02
  • java?常規(guī)輪詢長輪詢Long?polling實現(xiàn)示例詳解

    java?常規(guī)輪詢長輪詢Long?polling實現(xiàn)示例詳解

    這篇文章主要為大家介紹了java?常規(guī)輪詢長輪詢Long?polling實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • 使用Maven Helper解決Maven插件沖突的方法

    使用Maven Helper解決Maven插件沖突的方法

    這篇文章主要介紹了使用Maven Helper解決Maven插件沖突的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-12-12
  • jstl標簽基礎(chǔ)開發(fā)步驟(詳解)

    jstl標簽基礎(chǔ)開發(fā)步驟(詳解)

    下面小編就為大家?guī)硪黄猨stl標簽基礎(chǔ)開發(fā)步驟(詳解)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-05-05
  • 簡單了解JavaCAS的相關(guān)知識原理

    簡單了解JavaCAS的相關(guān)知識原理

    這篇文章主要介紹了簡單了解JavaCAS的相關(guān)知識,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-11-11
  • Spring AOP的幾種實現(xiàn)方式總結(jié)

    Spring AOP的幾種實現(xiàn)方式總結(jié)

    本篇文章主要介紹了Spring AOP的幾種實現(xiàn)方式總結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-02-02
  • Springboot解決跨域問題方案總結(jié)(包括Nginx,Gateway網(wǎng)關(guān)等)

    Springboot解決跨域問題方案總結(jié)(包括Nginx,Gateway網(wǎng)關(guān)等)

    跨域問題是瀏覽器為了保護用戶的信息安全,實施了同源策略(Same-Origin?Policy),即只允許頁面請求同源(相同協(xié)議、域名和端口)的資源,本文給大家總結(jié)了Springboot解決跨域問題方案包括Nginx,Gateway網(wǎng)關(guān)等),需要的朋友可以參考下
    2024-03-03

最新評論