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