Java-进阶:多线程1
目錄
一、概述
二、Thread 類
三、創建線程:繼承 Thread 類
四、創建線程:實現 Runnable 接口
五、線程優先級
六、線程的生命周期
七、同步代碼塊
一、概述
1. 進程和線程
- 進程:進程指正在運行的程序。
- 線程:線程是進程中的一個執行單元,是程序?使用cpu?的基本單位(調度)。負責當前進程中程序的執行。是進程中單個順序控制流(執行路徑),是一條單獨執行的路徑
- 一個程序運行后至少有一個進程,一個進程中可以包含多個線程
- 在操作系統中,進程是資源分配的基本單位,線程是調度的基本單位。
在沒有出現線程之前,進程既是操作系統進行資源分配的基本單位,又是調度的基本單位
- 單線程程序:若有多個任務只能依次執行。當上一個任務執行結束后,下一個任務開始執行。程序只有一條執行路徑
- 多線程程序:若有多個任務可以同時執行。程序有多條執行路徑
操作系統發展:單道批處理操作——>多道批處理操作系統——>分時操作系統(多進程的)——>線程?
批處理:程序在執行過程中,不會響應用戶的請求?
單道批處理操作:一次只能運行一個程序,如果要在計算機運行多個程序,這多個程序,只能一個一個的順序執行,如果這個正在運行的程序,在運行過程中,執行了一些非常耗時的IO操作(傳輸數據的過程是沒有使用到cpu),這樣一來,cpu就閑下來了,但是cpu是計算機中,最為昂貴的?
多道批處理操作系統:同時運行多個程序,顯著的提高了cpu的利用率,但是我們一旦一個,程序運行起來,都是是需要使用計算機資源的?
分時操作系統:每一個進程,有一個固定的時間片,在運行一個固定的時間片后,緊接著輪到下一個進程運行(切換)
- 為什么還會有線程呢??進程切換的代價太高了,這樣一來,每一次進程切換,都需要付出不小的額外的代價,為了減小進程切換的代價,引入了線程,提高CPU的利用率
2. 線程調度
- 分時調度:所有線程?輪流使用?CPU 的使用權,平均分配每個線程占用 CPU 的時間。
- 搶占式調度:優先讓?優先級高的?線程使用 CPU,如果線程的優先級相同,那么會隨機選擇一個(線程隨機性),Java使用的為搶占式調度
- 體現了:程序運行的不確定性
1. CPU 使用搶占式調度模式在多個線程間進行著高速的切換。對于CPU的一個核而言,某個時刻,只能執行一個線程,而 CPU的在多個線程間切換速度相對我們的感覺要快,看上去就是在同一時刻運行。
2. 多線程程序并不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。
3. 線程控制
- 線程睡眠static void sleep(long millis):在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行)
- 線程加入public final void join():(讓當前線程)等待該線程(新加入的線程終止)終止。
- 線程禮讓public static void yield():暫停當前正在執行的線程對象,并執行其他線程
只是讓當前線程放棄cpu執行權,但是不能阻止它放棄后繼續搶奪cpu執行權
- 后臺線程(守護線程)public final void setDaemon(boolean on):將該線程標記為守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出;該方法必須在啟動線程前調用。
- 中斷線程:public void interrupt():讓一個線程控制另外一個線程(有條件的:受阻),可以利用該方法終止另一個線程的運行
1. 如果線程在調用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法,或者該類的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 等阻塞方法處于阻塞狀態,它還將收到一個 InterruptedException
2. 中斷一個不處于活動狀態的線程不需要任何作用。?
中斷一個不處于阻塞狀態的線程,沒有其他任何效果
4. JAVA程序的運行原理
- Java命令會啟動 JVM,即啟動了一個進程,該進程會啟動一個主線程,然后主線程調用某個類的?main方法,所以 main方法 都是運行在主線程里
- jvm 啟動后,必然有一個執行路徑(線程)從 main方法開始的,一直執行到 main方法結束,這個線程在Java中稱之為主線程。
- 當程序的主線程執行時,如果遇到了循環而導致程序在指定位置停留時間過長,則無法馬上執行下面的程序,需要等待循環結束后能夠執行
- 方法在哪個線程中被調用,它就運行在哪個線程中
- JVM 是一個多線程程序,每個Java 進程都分配一個 JVM 實例
二、Thread 類
1. 概述
- Thread是程序中的執行線程。Java 虛擬機允許應用程序并發地運行多個執行線程
- 不是抽象類
2. 構造方法
- Thread(): 分配新的 Thread 對象
- Thread(String name):分配新的 Thread 對象,將指定的 name 作為其線程名稱
3. 常用方法
- void start():使該線程開始執行,Java虛擬機調用線程的 run 方法
- void run():該線程要執行的操作,
- static void sleep(long millis):在指定毫秒內讓當前正在執行的線程休眠,暫停執行
- static Thread currentThread():返回當前正在執行的線程對象的引用Thread.currentThread()
4. 創建新執行線程的兩種方法
- 將類聲明為?Thread?的子類。該子類應重寫 Thread 類的?run?方法。創建對象,開啟線程。run方法相當于其他線程的main方法。
- 聲明一個實現?Runnable?接口的類。該類然后實現?run?方法。然后創建 Runnable 的子類對象,傳入到某個線程的構造方法中,開啟線程。
雖然實現線程有兩種方式,其實從客觀來講,線程本身只代表獨立的執行路徑, 執行的具體內容其實是Task本身,和執行路徑的實現本身沒有聯系;只是我們開發者,想將一個task放在某條獨立的執行路徑(Thread 類對象,也就是一個線程中)來運行
三、創建線程:繼承 Thread 類
-
創建線程的步驟
-
定義一個類繼承 Thread
-
重寫 run方法
-
創建子類對象,就是創建線程對象
-
調用 start 方法,開啟線程并讓線程執行,同時還會告訴jvm去調用 run 方法
線程對象調用?run方法?不開啟線程。僅是對象調用方法。?
線程對象調用?start?開啟線程,并讓 jvm 調用 run 方法在開啟的線程中執行
四、創建線程:實現 Runnable 接口
1. Runnable 接口的構造方法
- Thread(Runnable target): 分配新的 Thread 對象,以便將 target 作為其運行對象
- Thread(Runnable target,String name)?: 分配新的 Thread 對象,以便將 target 作為其運行對象;并將指定的 name 作為其名稱
2. 創建線程的步驟
- 定義類實現?Runnable?接口。
- 覆蓋接口中的?run方法
- 創建 Thread類的?對象
- 將 Runnable接口 的子類對象作為參數傳遞給?Thread 類?的構造方法。
- 調用 Thread類的?start()?開啟線程。
Thread 類的構造函數:
1.?Thread(): 分配新的 Thread 對象
2.?Thread(String name):分配新的 Thread 對象,將指定的 name 作為其線程名稱
3. 實現 Runnable 接口的原理
為什么需要定一個類去實現Runnable接口呢?繼承Thread類和實現Runnable接口有啥區別呢??
只有創建Thread類的對象才可以創建線程。線程任務已被封裝到Runnable接口的run方法中,而這個run方法所屬于Runnable接口的子類對象,所以將這個子類對象作為參數傳遞給Thread的構造函數,這樣,線程對象創建時就可以明確要運行的線程的任務
4. 兩種方式的比較
-
繼承 Thread 類方式
-
如果某個類已經有父類,則無法再繼承 Thread 類
-
實現 Runnable 接口方式
-
解決了方式一的單繼承的局限性
-
還有一個優點,便于多線程共享數據
第二種方式實現Runnable接口避免了單繼承的局限性,所以較為常用。實現Runnable接口的方式,更加的符合面向對象;線程分為兩部分,一部分線程對象,一部分線程任務。
- 繼承Thread類,線程對象和線程任務耦合在一起。一旦創建Thread類的子類對象,既是線程對象,有又有線程任務。
- 實現runnable接口,將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。Runnable接口對線程對象和線程任務進行解耦
五、線程優先級
1. 概述
-
我們可以通過?Thread?類中:
-
getPriority 方法?:獲取?線程的優先級
-
setPriority 方法?:設置?線程的優先級
2. 線程優先級的范圍
- 如果設置線程優先級的范圍,超出了規定范圍,會拋出異常;
MAX_PRIORITY 10 //最大優先級?
MIN_PRIORITY 1 //最小優先級?
NORM_PRIORITY 5 //默認優先級
3. 注意!
- 對于 TThread 類中的優先級,對于jvm而言,不起決定性作用!
也就是說 jvm 在實際調度線程的時候,它使用的優先級可能不僅僅包含我們給每個線程所設置的靜態優先級,可能還考慮了其他的很多因素(各個線程的運行狀態),所以我們所設置的優先級對于 jvm 只起一個參考作用
六、線程的生命周期
1. 新建狀態
- 創建線程對象
2. 就緒狀態
- ?start() 之后
- sleep() 睡醒之后
- yield() 之后
- 有執行資格,等待cpu調度
3. 運行狀態
- 取得執行權,開始執行
4. 阻塞狀態
- 無執行資格,無執行權
5. 死亡狀態
- 執行完畢,等待垃圾回收
七、同步代碼塊
1. 問題引入——線程安全問題
- 發生線程安全問題的三個條件:
1. 多線程運行環境
2. 多線程訪問線程共享數據(存在共享數據)
3. 訪問共享數據的操作不是原子操作。?
注:原子操作:不可分割的操作?,相當于一次性完成的操作
- 當這三個條件同時滿足的時候,才會發生多線程的數據安全問題
- 解決多線程的線程安全問題:如何實現線程對共享資源的排他性訪問(只有我訪問完了你們才能修改)?
- 使用?同步代碼塊
2. 同步代碼塊
- 同步代碼塊實現?線程同步
- 線程同步:就是利用鎖對象,完成多線程運行環境中,對共享資源的排他性訪問(我走你不能走, 你走我不能走)
- 優點:解決了多線程的安全問題
- 缺點:消耗資源(當線程很多時,每個線程運行的時候都需要去判斷同步鎖,這個是很耗費系統資源的)
線程異步:線程之間,互不干擾,各自獨立運行(我走我的,你走你的)
- 格式:
1. 同步代碼塊中所使用的對象,稱之為鎖對象?——> 鎖的角色?
鎖對象中,有一個標志位:可以表示兩種狀態,加鎖?和?解鎖
2. 持有鎖的是線程(一個線程給一個鎖對象加鎖,我們就說這個線程持有了鎖對象)
3. 同步代碼塊何時給鎖對象加鎖?進入同步代碼塊的同時,就給鎖對象加鎖
4. 線程何時釋放持有的鎖??當執行完同步代碼塊的時候,就會釋放同步代碼塊的鎖對象
5. 判斷和修改鎖對象標志位的操作,這是由 jvm?保證一定是原子操作?
6.**?鎖對象,究竟是什么對象**? java語言中任意一個對象,都可以充當鎖對象的角色(因為任意一個對象中都有一個表示加鎖,解鎖狀態的標志位)
- 注意點:
雖然鎖對象可以是任意對象,但是針對同一個(同樣的多個共享變量)?的所有操作,都必須保證在同步代碼塊中,使用同一個鎖對象,才能避免線程安全問題。
3. 同步方法
- 當同步代碼塊的范圍擴大到整個方法的方法體的時候,我們可以將整個方法定義成同步方法,在方法聲明上加上synchronized
- 普通的同步代碼塊:?synchronized(鎖對象) {}
- 同步方法:?void synchronized 方法名(){}
- 同步方法的鎖對象就是?this
靜態方法是否可以定義為同步方法??
靜態的同步方法, 在方法聲明上加上static synchronized?
**,都可以充當鎖對象的角色(因為任意一個對象中都有一個表示加鎖,解鎖狀態的標志位)
- 注意點:
雖然鎖對象可以是任意對象,但是針對同一個(同樣的多個共享變量)?的所有操作,都必須保證在同步代碼塊中,使用同一個鎖對象,才能避免線程安全問題。
3. 同步方法
- 當同步代碼塊的范圍擴大到整個方法的方法體的時候,我們可以將整個方法定義成同步方法,在方法聲明上加上synchronized
- 普通的同步代碼塊:?synchronized(鎖對象) {}
- 同步方法:?void synchronized 方法名(){}
- 同步方法的鎖對象就是?this
靜態方法是否可以定義為同步方法??
靜態的同步方法, 在方法聲明上加上static synchronized
總結
以上是生活随笔為你收集整理的Java-进阶:多线程1的全部內容,希望文章能夠幫你解決所遇到的問題。