国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java 多線程編程基礎——Thread 類

zhoutk / 3213人閱讀

摘要:程序執行時,至少會有一個線程在運行,這個運行的線程被稱為主線程。程序的終止是指除守護線程以外的線程全部終止。多線程程序由多個線程組成的程序稱為多線程程序。線程休眠期間可以被中斷,中斷將會拋出異常。

線程
我們在閱讀程序時,表面看來是在跟蹤程序的處理流程,實際上跟蹤的是線程的執行。

單線程程序

在單線程程序中,在某個時間點執行的處理只有一個。

Java 程序執行時,至少會有一個線程在運行,這個運行的線程被稱為主線程(Main Thread)。

Java 程序在主線程運行的同時,后臺線程也在運行,例如:垃圾回收線程、GUI 相關線程等。

Java 程序的終止是指除守護線程(Daemon Thread)以外的線程全部終止。守護線程是執行后臺作業的線程,例如垃圾回收線程。我們可以通過 setDaemon() 方法把線程設置為守護線程。

多線程程序

由多個線程組成的程序稱為多線程程序(Multithreaded Program)。多個線程運行時,各個線程的運行軌跡將會交織在一起,同一時間點執行的處理有多個。

多線程應用場景:

GUI 應用程序:存在專門執行 GUI 操作的線程(UI Thread)

耗時任務:文件與網絡的 I/O 處理

網絡服務器同時處理多個客戶端請求場景

P.S. 使用 java.nio 包中的類,有時即便不使用線程,也可以執行兼具性能和可擴展性的 I/O 處理。

并行(parallel)與并發(concurrent)的區別

程序運行存在順序、并行與并發模式。

順序(sequential)用于表示多個操作依次處理。

并行用于表示多個操作同時處理,取決于 CPU 的個數。

并發用于表示將一個操作分割成多個部分并且允許無序處理。

并發相對于順序和并行來說比較抽象。單個 CPU 并發處理即為順序執行,多個 CPU 并發處理可以并行執行。

如果是單個 CPU,即便多個線程同時運行,并發處理也只能順序執行,在線程之間不斷切換。

并發處理包括:并發處理的順序執行、并發處理的并行執行。

線程和進程的區別

線程之間共享內存
進程和線程之間最大的區別就是內存是否共享。
通常,每個進程都擁有彼此獨立的內存空間。一個進程不可以擅自讀取、寫入其他進程的內存。正因為每個進程內存空間獨立,無需擔心被其他進程破壞。
線程之間共享內存,使得線程之間的通信實現起來更加自然、簡單。一個線程向實例中寫入內容,其他線程就可以讀取該實例的內容。當有多個線程可以訪問同一個實例時,需要正確執行互斥處理。

線程的上下文切換快
進程和線程之間的另一個區別就是上下文切換的繁重程度。
當運行中的進程進行切換時,進程要暫時保存自身的當前狀態(上下文信息)。而接著開始運行的進程需要恢復之前保存的自身的上下文信息。
當運行中的線程進行切換時,與進程一樣,也會進行上下文切換。但由于線程管理的上下文信息比進程少,所以一般來說,線程的上下文切換要比進程快。

當執行緊密關聯的多項工作時,通常線程比進程更加適合。

多線程程序的優點和成本

優點:

充分利用硬件資源如多核 CPU、I/O 設備、網絡設備并行工作。

提高 GUI 應用程序響應性,UI Thread 專注界面繪制、用戶交互,額外開啟線程執行后臺任務。

網絡應用程序簡化建模,每個客戶端請求使用多帶帶的線程進行處理。

缺點(成本):

創建線程需要消耗系統資源和時間,準備線程私有的程序計數器和棧。

線程調度和切換同樣需要成本,線程切換出去時需要保存上下文狀態信息,以便再次切換回來時能夠恢復之前的上下文狀態。

相對而言,若是存在耗時任務需要放入子線程中實際執行,線程使用成本可以不計。

多線程編程的重要性

硬件條件滿足多線程并行執行的條件之外,還需要程序邏輯能夠保證多線程正確地運行,考慮到線程之間的互斥處理和同步處理。

Thread 類 線程的創建與啟動

創建與啟動線程的兩種方法:

利用 Thread 類的子類實例化,創建并啟動線程。

利用 Runnable 接口的實現類實例化,創建并啟動線程。

線程的創建與啟動步驟——方法一:

聲明 Thread 的子類(extends Thread),并重寫 run() 方法。

創建該類的實例

調用該實例的 start() 方法啟動線程

Thread 實例和線程本身不是同一個東西,創建 Thread 實例,線程并未啟動,直到 start() 方法調用,同樣就算線程終止了,實例也不會消失。但是一個 Thread 實例只能創建一個線程,一旦調用 start() 方法,不管線程是否正常/異常結束,都無法再次通過調用 start() 方法創建新的線程。并且重復調用 start() 方法會拋出 IllegalThreadStateException 異常。

Thread run( ) 方法 和 start() 方法:

run() 方法是可以重復調用的,但是不會啟動新的線程,于當前線程中執行。run() 方法放置于 Runnable 接口旨在封裝操作。

start() 方法主要執行以下操作:啟動新的線程,并在其中調用 run() 方法。

線程的創建與啟動步驟——方法二:

聲明類并實現 Runnable 接口(implements Runnable),要求必須實現 run() 方法。

創建該類的實例

以該實例作為參數創建 Thread 類的實例 Thread(Runnable target)

調用 Thread 類的實例的 start() 方法啟動線程

不管是利用 Thread 類的子類實例化的方法(1),還是利用 Runnable 接口的實現類實例化的方法(2),啟動新線程的方法最終都是 Thread 類的 start() 方法。

Java 中存在單繼承限制,如果類已經有一個父類,則不能再繼承 Thread 類,這時可以通過實現 Runnable 接口來實現創建并啟動新線程。

Thread 類本身實現了 Runnable 接口,并將 run() 方法的重寫(override)交由子類來完成。

線程的屬性

id 和 name

通過 Thread(String name) 構造方法或 void setName(String name),給 Thread 設置一個友好的名字,可以方便調試。

優先級

Java 語言中,線程的優先級從1到10,默認為5。但因程序實際運行的操作系統不同,優先級會被映射到操作系統中的取值,因此 Java 語言中的優先級主要是一種建議,多線程編程時不要過于依賴優先級。

線程的狀態

Thread.State 枚舉類型(Enum)包括:

NEW
線程實例化后,尚未調用 start() 方法啟動。

RUNNABLE
可運行狀態,正在運行或準備運行。

BLOCKED
阻塞狀態,等待其他線程釋放實例的鎖。

WAITING
等待狀態,無限等待其他線程執行特定操作。

TIMED_WAITING
時限等待狀態,等待其他線程執行指定的有限時間的操作。

TERMINATED
線程運行結束

線程的方法 currentThread() 方法

Thread 類的靜態方法 currentThread() 返回當前正在執行的線程對象。

sleep() 方法

Thread 類的靜態方法 sleep() 能夠暫停(休眠)當前線程(執行該語句的線程)運行,放棄占用 CPU。線程休眠期間可以被中斷,中斷將會拋出 InterruptedException 異常。sleep() 方法的參數以毫秒作為單位,不過通常情況下,JVM 無法精確控制時間。

sleep() 方法調用需要放在 try catch 語句中,可能拋出 InterruptedException 異常。InterruptedException 異常能夠取消線程處理,可以使用 interrupt() 方法在中途喚醒休眠狀態的線程。

多線程示例程序中經常使用 sleep() 方法模擬耗時任務處理過程。

yield() 方法

Thread 類的靜態方法 yield() 能夠暫停當前線程(執行該語句的線程)運行,讓出 CPU 給其他線程優先執行。如果沒有正在等待的線程,或是線程的優先級不高,當前線程可能繼續運行,即 yield() 方法無法確保暫停當前線程。yield() 方法類似 sleep() 方法,但是不能指定暫停時間。

join() 方法

Thread 類的實例方法,持有 Thread 實例的線程,將會等待調用 join() 方法的 Thread 實例代表的線程結束。等待期間可以被中斷,中斷將會拋出 InterruptedException 異常。

示例程序:

public class HelloThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello");
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new HelloThread();
        thread.start();
        thread.join();
    }
}

main() 方法所在的主線程將會等待 HelloThread 子線程執行 run() 方法結束后再執行,退出程序。

并發編程特性

原子性

可見性

有序性

原子性操作問題
原子性概念來源于數據庫系統,一個事務(Transaction)中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被恢復(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

并發編程的原子性指對于共享變量的操作是不可分的,Java 基本類型除 long、double 外的賦值操作是原子操作。

非原子操作例如:

counter++;

讀取 counter 的當前值

在當前值基礎上加1

將新值重新賦值給 counter

Java 語言的解決方式:

使用 synchronized 關鍵字

使用 java.util.concurrent.atomic

內存可見性問題

計算機結構中,CPU 負責執行指令,內存負責讀寫數據。CPU 執行速度遠超內存讀寫速度,緩解兩者速度不一致引入了高速緩存。 預先拷貝內存數據的副本到緩存中,便于 CPU 直接快速使用。

因此計算機中除內存之外,數據還有可能保存在 CPU 寄存器和各級緩存當中。這樣一來,當訪問一個變量時,可能優先從緩存中獲取,而非內存;當修改一個變量時,可能先將修改寫到緩存中,稍后才會同步更新到內存中。

對于單線程程序來說沒有太大問題,但是多線程程序并行執行時,內存中的數據將會不一致,最新修改可能尚未同步到內存中。需要提供一種機制保證多線程對應的多核 CPU 緩存中的共享變量的副本彼此一致——緩存一致性協議。

Java 語言的解決方式:

使用 volatile 關鍵字

使用 synchronized 關鍵字

如果只是解決內存可見性問題,使用 synchronized 關鍵字成本較高,考慮使用 volatile 關鍵字更輕量級的方式。

指令重排序問題

有序性:即程序執行的順序嚴格按照代碼的先后順序執行。

Java 允許編譯器和處理器為了提高效率對指令進行重排序,重排序過程不會影響到單線程程序的執行,卻會可能影響到多線程程序并發執行時候的正確性。

volatile 關鍵字細節

Java 使用 volatile 關鍵字修飾變量,保證可見性、有序性。

保證變量的值一旦被修改后立即更新寫入內存,同時默認從內存讀取變量的值。(可見性)

禁止指令重排序(有序性)

但是 volatile 關鍵字無法保證對變量操作是原子性的。

線程的互斥處理(synchronized 關鍵字細節)

每個線程擁有獨立的程序計數器(指令執行行號)、棧(方法參數、局部變量等信息),多個線程共享堆(對象),這些區域對應 JVM 內存模型。當多個線程操作堆區的對象時候,可能出現多線程共享內存的問題。

競態條件

銀行取款問題
if(可用余額大于等于取款金額) {

可用余額減去取款金額

}
多個線程同時操作時,余額確認(可用余額大于等于取款金額)和取款(可用余額減去取款金額)兩個操作可能穿插執行,無法保證線程之間執行順序。

線程 A 線程 B
可用余額(1000)大于等于取款金額(1000)?是的 切換執行線程 B
線程 A 處于等待狀態 可用余額(1000)大于等于取款金額(1000)?是的
線程 A 處于等待狀態 可用余額減去取款金額(1000-1000 = 0)
切換執行線程 A 線程 B 結束
可用余額減去取款金額(0 - 1000 = -1000) 線程 B 結束

當有多個線程同時操作同一個對象時,可能出現競態條件(race condition),無法預期最終執行結果,與執行操作的時序有關,需要“交通管制”——線程的互斥處理。

Java 使用 synchronized 關鍵字執行線程的互斥處理。synchronized 關鍵字可以修飾類的實例方法、靜態方法和代碼塊。

synchronized 關鍵字保護的是對象而非方法、代碼塊,使用鎖來執行線程的互斥處理。

synchronized 修飾靜態方法和實例方法時保護的是不同的對象:

synchronized 修飾實例方法是使用該類的實例對象 this。

synchronized 修飾靜態方法是使用該類的類對象 class。

每個對象擁有一個獨立的鎖,同一對象內的所有 synchronized 方法共用。

synchronized 方法注意事項

基于 synchronized 關鍵字保護的是對象原則,有如下推論:

一個實例中的 synchronized 方法每次只能由一個線程運行,而非 synchronized 方法則可以同時由多線程運行。

一個實例中的多個 synchronized 方法同樣無法多線程運行。

不同實例中的 synchronized 方法可以同時由多線程運行。

synchronized 修飾的靜態方法(this 對象)和實例方法(class 對象)之間,可以同時被多線程執行。

synchronized 方法同步使用

synchronized 方法具有可重入性,即獲取鎖后可以在一個 synchronized 方法,調用其他需要同樣鎖的 synchronized 方法。

一般在保護實例變量時,將所有訪問該變量的方法設置為 synchronized 同步方法。

如果只是想讓方法中的某一部分由一個線程運行,而非整個方法,則可使用 synchronized 代碼塊,精確控制互斥處理的執行范圍。

synchronized 方法執行流程

嘗試獲取對象鎖,如果獲取到鎖進入2,未獲取到鎖則加入鎖的等待隊列進入阻塞狀態等待被喚醒。

執行 synchronized 方法

釋放對象鎖,如果等待隊列存在線程正在等待獲取鎖,將其喚醒,當有多個線程處于等待隊列,無法明確喚醒某一個,由多個線程競爭獲取。

死鎖

死鎖是指兩個或兩個以上的進程(線程)在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。

死鎖產生的四個必要條件

互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。

請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。

不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。

循環等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合 {P0,P1,P2,···,Pn} 中的 P0 正在等待一個 P1 占用的資源;P1 正在等待 P2 占用的資源,……,Pn 正在等待已被 P0 占用的資源。

產生死鎖必須同時滿足上述四個條件,只要其中任一條件不成立,死鎖可避免。

應該盡量避免在持有一個鎖的同時,申請另一個鎖。如果確實需要多個鎖,應該按照相同的順序獲取鎖。

線程的協作(wait()、notify() 方法細節)

多線程之間除了在競爭中做互斥處理,還需要相互協作。協作的前提是清楚共享的條件變量。

wait()、notify()、notifyAll() 都是 Object 類的實例方法,而不是 Thread 類中的方法。這三個方法與其說是針對線程的操作,倒不如說是針對實例的條件等待隊列的操作。

操作 obj 條件等待隊列中的線程(喚醒、等待):

obj.wait() 將當前線程放入 obj 的條件等待隊列。

obj.notify() 從 obj 的條件等待隊列喚醒一個線程。

obj.notifyAll() 喚醒 obj 條件等待隊列中的所有線程。

wait() 等待方法

每個對象擁有一個鎖和鎖的等待隊列,另外還有一個表示條件的等待隊列,用于線程間的協作。調用 wait() 方法會將當前線程放入條件隊列等待,等待條件需要等待時間或者依靠其他線程改變(notify()/notifyAll() )。等待期間同樣可以被中斷,中斷將會拋出 InterruptedException 異常。

Object 類的 wait() 方法和 Thread 類的 sleep() 方法在控制線程上主要區別在于對象鎖是否釋放,從方法所屬類可以看出 Object 類的 wait() 方法包含對象鎖管理機制。

wait() 實例方法用于線程間通信協作

sleep() 靜態方法用于暫停當前線程

兩者均會放棄占用 CPU

wait() 方法執行過程

將當前線程放入條件隊列等待,釋放對象鎖。

當前線程進入 WAITINGTIMED_WAITING 狀態。

等待時間或者被其他線程喚醒(notify()/notifyAll() ),從條件隊列中移除等待線程。

喚醒的線程獲得對象鎖,進入 RUNNABLE 狀態,從 wait() 方法返回,重新執行等待條件檢查。

喚醒的線程無法獲得對象鎖,進入 BLOCKED 狀態,加入對象鎖的等待隊列,繼續等待。

notify() 喚醒方法

notify() 和 notifyAll() 方法的區別

notify() 方法會喚醒等待隊列中的一個線程。

notifyAll() 方法會喚醒等待隊列中所有線程。

通常使用 notifyAll() 方法,相比于 notify() 方法代碼更具健壯性,但是喚醒多個線程速度慢些。

注意:調用 notify() 方法之后,喚醒條件隊列中等待的線程,并將其移除隊列。被喚醒的線程并不會立即運行,因為執行 notify() 方法的線程還持有著鎖,等待 notify() 方法所處的同步(synchronized)代碼塊執行結束才釋放鎖。隨后等待的線程獲得鎖從 wait() 方法返回,重新執行等待條件檢查。

總結:

線程必須持有實例的鎖才能執行上述方法(wait()、notify()、notifyAll())

wait()/notify() 方法只能在 synchronized 代碼塊內被調用,如果調用 wait()/notify() 方法時,當前線程沒有持有對象鎖,會拋出異常 java.lang.IllegalMonitorStateException

生產者/消費者模式應用

生產者(Producer)生成數據的線程

消費者(Consumer)使用數據的線程

生產者線程和消費者線程通過共享隊列進行協作,

生產者/消費者模式在生產者和消費者之間加入了一個橋梁角色,該橋梁角色用于消除線程間處理速度的差異。

Channel 角色持有共享隊列 Data,對 Producer 角色和 Consumer 角色的訪問執行互斥處理,并隱藏多線程實現。

線程的中斷

線程正常結束于 run() 方法執行完畢,但在實際應用中多線程模式往往是死循環,考慮到存在特殊情況需要取消/關閉線程。Java 使用中斷機制,通過協作方式傳遞信息,從而取消/關閉線程。

中斷的方法
public static boolean interrupted()
public boolean isInterrupted()
public void interrupt()

interrupt() 和 isInterrupted() 是實例方法,通過線程對象調用。

interrupted() 是靜態方法,由當前線程 Thread.currentThread() 實際執行。

線程存在 interrupted 中斷狀態標記,用于判斷線程是否中斷。

isInterrupted() 實例方法返回對應線程的中斷狀態。

interrupted() 靜態方法返回當前線程的中斷狀態,存在副作用清空中斷狀態。

不同線程狀態的中斷反應

NEWTERMINATED
調用 interrupt() 方法不起任何作用

RUNNABLE
調用 interrupt() 方法,線程正在運行,且與 I/O 操作無關,設置線程中斷狀態標記而已。如果線程等待 I/O 操作,則會進行特殊處理。

BLOCKED
調用 interrupt() 方法無法中斷正在 BLOCKED 狀態的線程

WAITINGTIMED_WAITING
調用 interrupt() 方法設置線程中斷狀態標記,拋出 InterruptedException 異常。這是一個受檢異常,線程必須進行處理。

中斷的使用

對于提供線程服務的模塊,應該封裝取消/關閉線程方法對外提供接口,而不是交由調用者自行調用 interrupt() 方法。

線程狀態轉換綜合圖解

結合線程的方法(Thread 類 + Object 類)來看線程的狀態轉換:

注:

Thread t = new Thread(); Thread 類調用靜態方法,t 對象調用實例方法。

Object o = new Object(); Object 類調用靜態方法,o 對象調用實例方法。

Running 表示運行中狀態,并非 Thread.State 枚舉類型。

附錄 Runnable 接口和 Callable 接口

Runnable 接口提供的 run() 方法返回值為 void

Callable 接口提供的 call() 方法返回值為泛型

Callable 接口常用與配合 Future、FutureTask 類獲取異步執行結果。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71909.html

相關文章

  • JAVA 線程和并發基礎

    摘要:線程可以被稱為輕量級進程。一個守護線程是在后臺執行并且不會阻止終止的線程。其他的線程狀態還有,和。上下文切換是多任務操作系統和多線程環境的基本特征。在的線程中并沒有可供任何對象使用的鎖和同步器。 原文:Java Multi-Threading and Concurrency Interview Questions with Answers 翻譯:并發編程網 - 鄭旭東 校對:方騰飛 多...

    vboy1010 評論0 收藏0
  • JAVA 線程和并發基礎面試問答

    摘要:多線程和并發問題是技術面試中面試官比較喜歡問的問題之一。線程可以被稱為輕量級進程。一個守護線程是在后臺執行并且不會阻止終止的線程。其他的線程狀態還有,和。上下文切換是多任務操作系統和多線程環境的基本特征。 多線程和并發問題是 Java 技術面試中面試官比較喜歡問的問題之一。在這里,從面試的角度列出了大部分重要的問題,但是你仍然應該牢固的掌握Java多線程基礎知識來對應日后碰到的問題。(...

    dreamans 評論0 收藏0
  • Java并發編程筆記(一)

    摘要:并發編程實戰水平很高,然而并不是本好書。一是多線程的控制,二是并發同步的管理。最后,使用和來關閉線程池,停止其中的線程。當線程調用或等阻塞時,對這個線程調用會使線程醒來,并受到,且線程的中斷標記被設置。 《Java并發編程實戰》水平很高,然而并不是本好書。組織混亂、長篇大論、難以消化,中文翻譯也較死板。這里是一篇批評此書的帖子,很是貼切。俗話說:看到有這么多人罵你,我就放心了。 然而知...

    cnsworder 評論0 收藏0
  • 淺談Java并發編程系列(八)—— LockSupport原理剖析

    摘要:此對象在線程受阻塞時被記錄,以允許監視工具和診斷工具確定線程受阻塞的原因。阻塞當前線程,最長不超過納秒,返回條件在的基礎上增加了超時返回。喚醒線程喚醒處于阻塞狀態的線程。 LockSupport 用法簡介 LockSupport 和 CAS 是Java并發包中很多并發工具控制機制的基礎,它們底層其實都是依賴Unsafe實現。 LockSupport是用來創建鎖和其他同步類的基本線程阻塞...

    jeyhan 評論0 收藏0
  • JAVA并發編程--1.基礎概念

    摘要:線程線程,是程序執行流的最小單元。由于線程之間的相互制約,致使線程在運行中呈現出間斷性。線程的狀態機線程也有就緒阻塞和運行三種基本狀態。在單個程序中同時運行多個線程完成不同的工作,稱為多線程。可以視為不同線程競爭一把鎖。 進程線程協程 進程 進程是一個實體。每一個進程都有它自己的地址空間, 文本區域(text region) 數據區域(data region) 堆棧(stack re...

    abson 評論0 收藏0

發表評論

0條評論

zhoutk

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<