日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Java中的进程与线程

發(fā)布時間:2025/4/5 java 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java中的进程与线程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

2019獨(dú)角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>

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

概念

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

Java 進(jìn)程的建立方法

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

Runtime.exec 方法建立一個本地進(jìn)程

該方法在 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 方法來建立一個本地的進(jìn)程

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

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

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

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)建進(jìn)程的最大優(yōu)點。而且可以在后面的使用中隨著需要去改變代碼中 pb 變量的屬性。如果后續(xù)代碼修改了其屬性,那么會影響到修改后用 start 方法創(chuàng)建的進(jìn)程,對修改之前創(chuàng)建的進(jìn)程實例沒有影響。

JVM 對進(jìn)程的實現(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)的本地方法,然后對其進(jìn)行分析。JVM 對進(jìn)程的實現(xiàn)相對比較簡單,以 Windows 下的 JVM 為例。在 JVM 中,將 Java 中調(diào)用方法時的傳入的參數(shù)傳遞給操作系統(tǒng)對應(yīng)的方法來實現(xiàn)相應(yīng)的功能。

以 create 方法為例,我們看一下它是如何和系統(tǒng) API 進(jìn)行連接的。 在 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 所示 。

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; } /* 進(jìn)行參數(shù)格式的轉(zhuǎn)換 */ pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL); ……/* 調(diào)用系統(tǒng)提供的方法,建立一個 Windows 的進(jìn)程 */ 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 */ …/* 拿到新進(jìn)程的句柄 */ ret = (jlong)pi.hProcess; …/* 最后返回該句柄 */ return ret; }

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

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

通過上面對 Java 進(jìn)程的分析,其實它在實現(xiàn)上就是創(chuàng)建了操作系統(tǒng)的一個進(jìn)程,也就是每個 JVM 中創(chuàng)建的進(jìn)程都對應(yīng)了操作系統(tǒng)中的一個進(jìn)程。但是,Java 為了給用戶更好的更方便的使用,向用戶屏蔽了一些與平臺相關(guān)的信息,這為用戶需要使用的時候,帶來了些許不便。 在使用 C/C++ 創(chuàng)建系統(tǒng)進(jìn)程的時候,是可以獲得進(jìn)程的 PID 值的,可以直接通過該 PID 去操作相應(yīng)進(jìn)程。但是在 JAVA 中,用戶只能通過實例的引用去進(jìn)行操作,當(dāng)該引用丟失或者無法取得的時候,就無法了解任何該進(jìn)程的信息。

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

  • Java 提供的輸入輸出的管道容量是十分有限的,如果不及時讀取會導(dǎo)致進(jìn)程掛起甚至引起死鎖。
  • 當(dāng)創(chuàng)建進(jìn)程去執(zhí)行 Windows 下的系統(tǒng)命令時,如:dir、copy 等。需要運(yùn)行 windows 的命令解釋器,command.exe/cmd.exe,這依賴于 windows 的版本,這樣才可以運(yùn)行系統(tǒng)的命令。
  • 對于 Shell 中的管道 ‘ | ’命令,各平臺下的重定向命令符 ‘ > ’,都無法通過命令參數(shù)直接傳入進(jìn)行實現(xiàn),而需要在 Java 代碼中做一些處理,如定義新的流來存儲標(biāo)準(zhǔn)輸出,等等問題。
  • 總之,Java 中對操作系統(tǒng)的進(jìn)程進(jìn)行了封裝,屏蔽了操作系統(tǒng)進(jìn)程相關(guān)的信息。同時,在使用 Java 提供創(chuàng)建進(jìn)程運(yùn)行本地命令的時候,需要小心使用。

    一般而言,使用進(jìn)程是為了執(zhí)行某項任務(wù),而現(xiàn)代操作系統(tǒng)對于執(zhí)行任務(wù)的計算資源的配置調(diào)度一般是以線程為對象(早期的類 Unix 系統(tǒng)因為不支持線程,所以進(jìn)程也是調(diào)度單位,但那是比較輕量級的進(jìn)程,在此不做深入討論)。創(chuàng)建一個進(jìn)程,操作系統(tǒng)實際上還是會為此創(chuàng)建相應(yīng)的線程以運(yùn)行一系列指令。特別地,當(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)建一個線程:

    • 繼承 Thread 類
    • 實現(xiàn) Runnable 接口

    不管是用哪種方法,實際上都是要實現(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 虛擬機(jī)調(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)用,進(jìn)而注冊相應(yīng)的本地方法。

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

    本地方法 registerNatives 是定義在 Thread.c 文件中的。Thread.c 是個很小的文件,定義了各個操作系統(tǒng)平臺都要用到的關(guān)于線程的公用數(shù)據(jù)和操作,如代碼清單 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 所示。

    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 是如何聲明定義的,因為涉及到很繁瑣的代碼細(xì)節(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,由其再進(jìn)一步調(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)系。
  • 線程運(yùn)行時間統(tǒng)計 Java 沒有提供方法來獲取線程中某段代碼的運(yùn)行時間的統(tǒng)計結(jié)果。雖然可以自行使用計時的方法來實現(xiàn)(獲取運(yùn)行開始和結(jié)束的時間,然后相減 ),但由于存在多線程調(diào)度方法的原因,無法獲取線程實際使用的 CPU 運(yùn)算時間,因而必然是不準(zhǔn)確的。
  • 總結(jié)

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

    ========END========

    轉(zhuǎn)載于:https://my.oschina.net/xinxingegeya/blog/749867

    總結(jié)

    以上是生活随笔為你收集整理的Java中的进程与线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。