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

溫馨提示×

溫馨提示×

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

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

JAVA中多線程編程方法的實例

發布時間:2020-11-09 14:00:21 來源:億速云 閱讀:162 作者:小新 欄目:編程語言

JAVA中多線程編程方法的實例?這個問題可能是我們日常學習或工作經常見到的。希望通過這個問題能讓你收獲頗深。下面是小編給大家帶來的參考內容,讓我們一起來看看吧!

 一、程序、進程、線程

程序是一組指令的有序集合,也可以將其通俗地理解為若干行代碼。它本身沒有任何運行的含義,它只是一個靜態的實體,它可能只是一個單純的文本文件,也有可能是經過編譯之后生成的可執行文件。
??從狹義來說,進程是正在運行的程序的實例;從廣義上來說,進程是一個具有一定獨立功能的程序關于某個數據集合的一次運行活動。進程是操作系統進行資源分配的基本單位。
??線程是進程中可獨立執行的最小單位,它也是處理器進行獨立調度和分派的基本單位。一個進程可以包含多個線程,每個線程執行自己的任務,同一個進程中的所有線程共享該進程中的資源,如內存空間、文件句柄等。

二、多線程編程簡介

1、什么是多線程編程

多線程編程技術是Java語言的重要特點。多線程編程的含義是將程序任務分成幾個并行的子任務,并將這些子任務交給多個線程去執行。
??多線程編程就是以線程為基本抽象單位的一種編程范式。但是,多線程編程又不僅僅是使用多個線程進行編程那么簡單,其自身又有需要解決的問題。多線程編程和面向對象編程是可以相容的,即我們可以在面向對象編程的基礎上實現多線程編程。事實上,Java平臺中的一個線程就是一個對象

2、為什么要使用多線程編程

現在的計算機動輒就是多處理器核心的,而每一個線程同一時間只能運行在一個處理器上。如果只采用單線程進行開發,那么就不能充分利用多核處理器的資源來提高程序的執行效率。而使用多線程進行編程時,不同的線程可以運行在不同的處理器上。這樣一來,不僅大大提高了對計算機資源的利用率,同時也提高了程序的執行效率。

三、JAVA線程API簡介

java.lang.Thread類就是Java平臺對線程的實現。Thread類或其子類的一個實例就是一個線程。

1、線程的創建、啟動、運行

在Java平臺中,創建一個線程就是創建一個Thread類(或其子類)的示例。每個線程都有其要執行的任務。線程的任務處理邏輯可以在Thread類的run方法中直接實現或者通過該方法進行調用,因此run方法相當于線程的任務處理邏輯的入口方法,它應該由Java虛擬機在運行相應線程時直接調用,而不應該由應用代碼進行調用。
??運行一個線程實際上就是讓Java虛擬機執行該線程的run方法,從而使任務處理邏輯代碼得以執行。如果一個線程沒有啟動,它的run方法是絕對不會被執行的。為此,首先需要啟動線程。Thread類的start方法的作用是啟動相應的線程。啟動一個線程的實質是請求虛擬機運行相應的線程,而這個線程具體何時能夠運行是由線程調度器(線程調度器是操作系統的一部分)決定的。因此,調用線程的start方法并不意味著線程已經開始運行,這個線程可能馬上開始運行,也有可能稍后才被運行,也有可能永遠不運行。
??下面介紹兩種創建線程的方式(實際上還有其他方式,后續文章中會詳細介紹)。在此之前我們先來看一下Thread類的run方法的源碼:

// Code 1-1@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

這個run方法是在接口Runnable中定義的,它不接受參數也沒有返回值。事實上Runnable接口中也只有這一個方法,因此這個接口是一個函數式接口,這意味著我們可以在需要Runnable的地方使用lambda表達式。Thread類實現了這個接口,因此它必須實現這個方法。target是Thread類中的一個域,它的類型也是Runnable。target域表示這個線程需要執行的內容,而Thread類的run方法所做的也只是執行target的run方法。
??我們剛剛提到,Java虛擬機會自動調用線程的run方法。但是,Thread類的run方法已經定義好了,我們沒有辦法將自己需要執行的代碼放在Thread類的run方法中。因此,我們可以考慮其他的方式來影響run方法的行為。第一種就是繼承Thread類并重寫run方法,這樣JVM在運行線程時就會調用我們重寫的run方法而不是Thread類的run方法;第二種方法是將我們要執行的代碼傳遞給Thread類的target方法,而剛好Thread類有幾個構造器可以直接對target進行賦值,這樣一來,JVM在調用run方法時執行的仍然是我們傳遞的代碼。
??在Java平臺中,每個線程都可以擁有自己默認的名字,當然我們也可以在構造Thread類的實例時為我們的線程起一個名字,這個名字便于我們區分不同的線程。
??下面的代碼使用上述的兩種方式創建了兩個線程,它們要執行的任務很簡單——打印一行歡迎信息,并且要包含自己的名字。

public class WelcomeApp {
    public static void main(String[] args) {
        Thread thread1 = new WelcomeThread();
        Thread thread2 = new Thread(() -> System.out.println("2. Welcome, I'm " + Thread.currentThread().getName()));
        thread1.start();
        thread2.start();
    }
}class WelcomeThread extends Thread {
    @Override
    public void run() {
        System.out.println("1. Welcome, I'm " + Thread.currentThread().getName());
    }
}

下面是這個程序運行時輸出的內容:

1. Welcome, I'm Thread-0
2. Welcome, I'm Thread-1

多次運行這個程序,我們可以發現這個程序的輸出也有可能是:

2. Welcome, I'm Thread-1
1. Welcome, I'm Thread-0

這說明,雖然thread1的啟動在thread2之前,但這并不意味著thread1會在thread2之前被運行。
??不管采用哪種方式創建線程,一旦線程的run方法執行(由JVM調用)結束,相應線程的運行也就結束了。當然,run方法執行結束包括正常結束(run方法正常返回)和代碼中拋出異常而導致的終止。運行結束的線程所占用的資源(如內存空間)會如同其他Java對象一樣被JVM回收。
??線程屬于“一次性用品”,我們不能通過重新調用一個已經運行結束的線程的start方法來使其重新運行。事實上,start方法也只能夠被調用一次,多次調用同一個Thread實例的start方法會導致其拋出IllegalThreadStateException異常。

2、線程的屬性

線程的屬性包括線程的編號、名稱、類別和優先級, 詳情如下表所示:

JAVA中多線程編程方法的實例

上面提到了守護線程和用戶線程的概念,這里對它們做一個簡要的說明。按照線程是否會阻止Java虛擬機正常停止,我們可以將Java中的線程分為守護線程(Daemon Thread)和用戶線程(User Thread,也稱非守護線程)。線程的daemon屬性用于表示相應線程是否為守護線程。用戶線程會阻止Java虛擬機的正常停止,即一個Java虛擬機只有在其所有用戶線程都運行結束(即Thread.run()調用未結束)的情況下才能正常停止。而守護線程則不會影響Java虛擬機的正常停止,即應用程序中有守護線程在運行也不影響Java虛擬機的正常停止。因此,守護線程通常用于執行一些重要性不是很高的任務,例如用于監視其他線程的運行情況。
??當然,如果Java虛擬機是被強制停止的,比如在Linux系統下使用kill命令強制終止一個Java虛擬機進程,那么即使是用戶線程也無法阻止Java虛擬機的停止。

3、Thread類常用方法

JAVA中多線程編程方法的實例

Java中的任何一段代碼總是執行在某個線程之中。執行當前代碼的線程就被稱為當前線程,Thread.currentThread()可以返回當前線程。由于同一段代碼可能被不同的線程執行,因此當前線程是相對的,即Thread.currentThread()的返回值在代碼實際運行的時候可能對應著不同的線程(對象)。
??join方法的作用相當于執行該方法的線程和線程調度器說:“我得先暫停一下,等到另外一個線程運行結束后我才能繼續。”
??yield靜態方法的作用相當于執行該方法的線程對線程調度器說:“我現在不急,如果別人需要處理器資源的話先給它用吧。當然,如果沒有其他人要用,我也不介意繼續占用。”
??sleep靜態方法的作用相當于執行該方法的線程對線程調度器說:“我想小憩一會兒,過段時間再叫醒我繼續干活吧。”

4、Thread類中的廢棄方法

JAVA中多線程編程方法的實例

雖然這些方法并沒有相應的替代品,但是可以使用其他辦法來實現,我們會在后續文章中學習這部分內容。

四、無處不在的線程

Java平臺本身就是一個多線程的平臺。除了Java開發人員自己創建和使用的線程,Java平臺中其他由Java虛擬機創建、使用的線程也隨處可見。當然,這些線程也是各自有其處理任務。
??Java虛擬機啟動的時候會創建一個主線程(main線程),該線程負責執行Java程序的入口方法(main方法)。下面的程序打印出主線程的名稱:

public class MainThreadDemo {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
    }
}

??該程序會輸出“main”,這說明main方法是由一個名為“main”的線程調用的,這個線程就是主線程,它是由JVM創建并啟動的。
??在多線程編程中,弄清楚一段代碼具體是由哪個(或者哪種)線程去負責執行的這點很重要,這關系到性能、線程安全等問題。本系列的后續文章會體現這點。
??Java 虛擬機垃圾回收器(Garbage Collector)負責對Java程序中不再使用的內存空間進行回收,而這個回收的動作實際上也是通過專門的線程(垃圾回收線程)實現的,這些線程由Java虛擬機自行創建。
??為了提高Java代碼的執行效率,Java虛擬機中的JIT(Just In Time)編譯器會動態地將Java字節碼編譯為Java虛擬機宿主機處理器可直接執行的機器碼。這個動態編譯的過程實際上是由Java虛擬機創建的專門的線程負責執行的。
??Java平臺中的線程隨處可見,這些線程各自都有其處理任務。

五、線程的層次關系

Java平臺中的線程不是孤立的,線程與線程之間總是存在一些聯系。假設線程A所執行的代碼創建了線程B, 那么,習慣上我們稱線程B為線程A的子線程,相應地線程A就被稱為線程B的父線程。例如, Code 1-2中的線程thread1和thread2是main線程的子線程,main線程是它們的父線程。子線程所執行的代碼還可以創建其他線程,因此一個子線程也可以是其他線程的父線程。所以,父線程、子線程是一個相對的稱呼。理解線程的層次關系有助于我們理解Java應用程序的結構,也有助于我們后續闡述其他概念。
??在Java平臺中,一個線程是否是一個守護線程默認取決于其父線程:默認情況下父線程是守護線程,則子線程也是守護線程;父線程是用戶線程,則子線程也是用戶線程。另外,父線程在創建子線程后啟動子線程之前可以調用該線程的setDaemon方法,將相應的線程設置為守護線程(或者用戶線程)。
??一個線程的優先級默認值為該線程的父線程的優先級,即如果我們沒有設置或者更改一個線程的優先級,那么這個線程的優先級的值與父線程的優先級的值相等。
??不過,Java平臺中并沒有API用于獲取一個線程的父線程,或者獲取一個線程的所有子線程。并且,父線程和子線程之間的生命周期也沒有必然的聯系。比如父線程運行結束后,子線程可以繼續運行,子線程運行結束也不妨礙其父線程繼續運行。

六、線程的生命周期狀態

?在Java平臺中,一個線程從其創建、啟動到其運行結束的整個生命周期可能經歷若干狀態。如下圖所示:

JAVA中多線程編程方法的實例

線程的狀態可以通過Thread.getState()調用來獲取。Thread.getState()的返回值類型Thread.State,它是Thread類內部的一個枚舉類型。Thread.State所定義的線程狀態包括以下幾種:
??NEW:一個己創建而未啟動的線程處于該狀態。由于一個線程實例只能夠被啟動一次,因此一個線程只可能有一次處于該狀態。
??RUNNABLE:該狀態可以被看成一個復合狀態,它包括兩個子狀態:READY和RUNNING,但實際上Thread.State中并沒有定義這兩種狀態。前者表示處于該狀態的線程可以被線程調度器進行調度而使之處于RUNNING狀態。后者表示處于該狀態的線程正在運行,即相應線程對象的run方法所對應的指令正在由處理器執行。執行Thread.yield()的線程,其狀態可能會由RUNNING轉換為READY。處于READY子狀態的線程也被稱為活躍線程。
??BLOCKED:一個線程發起一個阻塞式I/0操作后,或者申請一個由其他線程持有的獨占資源(比如鎖)時,相應的線程會處于該狀態。處于BLOCKED狀態的線程并不會占用處理器資源。當阻塞式1/0操作完成后,或者線程獲得了其申請的資源,該線程的狀態又可以轉換為RUNNABLE。
??WAITING:一個線程執行了某些特定方法之后就會處于這種等待其他線程執行另外一些特定操作的狀態。能夠使其執行線程變更為WAITING狀態的方法包括:Object.wait()、Thread.join()和LockSupport.park(Object)。能夠使相應線程從WAITING變更為RUNNABLE的相應方法包括:Object.notify()/notifyAll()和LockSupport.unpark(Object))。
??TIMED_WAITING:該狀態和WAITING類似,差別在于處于該狀態的線程并非無限制地等待其他線程執行特定操作,而是處于帶有時間限制的等待狀態。當其他線程沒有在指定時間內執行該線程所期望的特定操作時,該線程的狀態自動轉換為RUNNABLE。
??TERMINATED:已經執行結束的線程處于該狀態。由于一個線程實例只能夠被啟動一次,因此一個線程也只可能有一次處于該狀態。run方法正常返回或者由于拋出異常而提前終止都會導致相應線程處于該狀態。
??一個線程在其整個生命周期中,只可能有一次處于NEW狀態和TERMINATED狀態。

七、多線程編程的優勢

多線程編程具有以下優勢:

提高系統的吞吐率:多線程編程使得一個進程中可以有多個并發(即同時進行的)的操作。例如,當一個線程因為I/0操作而處于等待時,其他線程仍然可以執行其操作。

提高響應性:在使用多線程編程的情況下,對于GUI軟件(如桌面應用程序)而言,一個慢的操作(比如從服務器上下載一個大的文件)并不會導致軟件的界面出現被“凍住”的現象而無法響應用戶的其他操作;對于Web應用程序而言,一個請求的處理慢了并不會影響其他請求的處理。

充分利用多核處理器資源:如今多核處理器的設備越來越普及,就算是手機這樣的消費類設備也普遍使用多核處理器。實施恰當的多線程編程有助于我們充分利用設備的多核處理器資源,從而避免了資源浪費。

多線程編程也有自身的問題與風險,包括以下幾個方面:

線程安全問題。多個線程共享數據的時候,如果沒有采取相應的并發訪問控制措施,那么就可能產生數據一致性問題,如讀取臟數據(過期的數據)、丟失更新(某些線程所做的更新被其他線程所做的更新覆蓋)等。

線程活性問題。一個線程從其創建到運行結束的整個生命周期會經歷若于狀態。從單個線程的角度來看,RUNNABLE狀態是我們所期望的狀態。但實際上,代碼編寫不當可能導致某些線程一直處于等待其他線程釋放鎖的狀態(BLOCKED狀態),這種情況稱為死鎖(Deadlock)。當然,一直忙碌的線程也可能會出現問題,它可能面臨活鎖(Livelock)問題,即一個線程一直在嘗試某個操作但就是無法進展。另外,線程是一種稀缺的計算資源,一個系統所擁有的處理器數最相比于該系統中存在的線程數量而言總是少之又少的。某些情況下可能出現線程饑餓(Starvation)的問題,即某些線程永遠無法獲取處理器執行的機會而永遠處于RUNNABLE狀態的READY子狀態。

上下文切換。處理器從執行一個線程轉向執行另外一個線程的時候操作系統所需要做的一個動作被稱為上下文切換。由于處理器資源的稀缺性,因此上下文切換可以被看作多線程編程的必然副產物,它增加了系統的消耗,不利于系統的吞吐率。

感謝各位的閱讀!看完上述內容,你們對JAVA中多線程編程方法的實例大概了解了嗎?希望文章內容對大家有所幫助。如果想了解更多相關文章內容,歡迎關注億速云行業資訊頻道。

向AI問一下細節

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

AI

文山县| 搜索| 通城县| 庄河市| 嫩江县| 临海市| 抚远县| 广德县| 房产| 长子县| 康马县| 信宜市| 宾阳县| 蛟河市| 华坪县| 炉霍县| 孙吴县| 信丰县| 浦北县| 兴和县| 商水县| 府谷县| 五寨县| 上蔡县| 汶川县| 牟定县| 涞源县| 肥东县| 寿阳县| 东阳市| 南和县| 团风县| 南平市| 东乌珠穆沁旗| 淳安县| 颍上县| 滕州市| 齐齐哈尔市| 永修县| 墨玉县| 内乡县|