多線程(筆記)

來(lái)源:拉勾教育Java就業(yè)集訓(xùn)營(yíng)

基本概念

程序和進(jìn)程的概念
  • 程序 - 數(shù)據(jù)結(jié)構(gòu) + 算法,主要指存放在硬盤(pán)上的可執(zhí)行文件。

  • 進(jìn)程 - 主要指運(yùn)行在內(nèi)存中的可執(zhí)行文件。

  • 目前主流的操作系統(tǒng)都支持多進(jìn)程,為了讓操作系統(tǒng)同時(shí)可以執(zhí)行多個(gè)任務(wù),但進(jìn)程是重量級(jí)的, 也就是新建一個(gè)進(jìn)程會(huì)消耗CPU和內(nèi)存空間等系統(tǒng)資源,因此進(jìn)程的數(shù)量比較局限。

線程的概念
  • 為了解決上述問(wèn)題就提出線程的概念,線程就是進(jìn)程內(nèi)部的程序流,也就是說(shuō)操作系統(tǒng)內(nèi)部支持多 進(jìn)程的,而每個(gè)進(jìn)程的內(nèi)部又是支持多線程的,線程是輕量的,新建線程會(huì)共享所在進(jìn)程的系統(tǒng)資 源,因此目前主流的開(kāi)發(fā)都是采用多線程。

  • 多線程是采用時(shí)間片輪轉(zhuǎn)法來(lái)保證多個(gè)線程的并發(fā)執(zhí)行,所謂并發(fā)就是指宏觀并行微觀串行的機(jī) 制。

線程的創(chuàng)建(重中之重)

Thread類(lèi)的概念
  • java.lang.Thread類(lèi)代表線程,任何線程對(duì)象都是Thread類(lèi)(子類(lèi))的實(shí)例。

  • Thread類(lèi)是線程的模板,封裝了復(fù)雜的線程開(kāi)啟等操作,封裝了操作系統(tǒng)的差異性。

創(chuàng)建方式
  • 自定義類(lèi)繼承Thread類(lèi)并重寫(xiě)run方法,然后創(chuàng)建該類(lèi)的對(duì)象調(diào)用start方法。

  • 自定義類(lèi)實(shí)現(xiàn)Runnable接口并重寫(xiě)run方法,創(chuàng)建該類(lèi)的對(duì)象作為實(shí)參來(lái)構(gòu)造Thread類(lèi)型的對(duì) 象,然后使用Thread類(lèi)型的對(duì)象調(diào)用start方法。

相關(guān)的方法
方法聲明 功能介紹
Thread() 使用無(wú)參的方式構(gòu)造對(duì)象
Thread(String name) 根據(jù)參數(shù)指定的名稱(chēng)來(lái)構(gòu)造對(duì)象
Thread(Runnable target) 根據(jù)參數(shù)指定的引用來(lái)構(gòu)造對(duì)象,其中Runnable是個(gè)接口類(lèi) 型
Thread(Runnable target, String name) 根據(jù)參數(shù)指定引用和名稱(chēng)來(lái)構(gòu)造對(duì)象
void run() 若使用Runnable引用構(gòu)造了線程對(duì)象,調(diào)用該方法時(shí)最終調(diào)用接口中的版本 若沒(méi)有使用Runnable引用構(gòu)造線程對(duì)象,調(diào)用該方法時(shí)則啥也不做(target不為null時(shí),target.run())
void start() 用于啟動(dòng)線程,Java虛擬機(jī)會(huì)自動(dòng)調(diào)用該線程的run方法
執(zhí)行流程
  • 執(zhí)行main方法的線程叫做主線程,執(zhí)行run方法的線程叫做新線程/子線程。

  • main方法是程序的入口,對(duì)于start方法之前的代碼來(lái)說(shuō),由主線程執(zhí)行一次,當(dāng)start方法調(diào)用成 功后線程的個(gè)數(shù)由1個(gè)變成了2個(gè),新啟動(dòng)的線程去執(zhí)行run方法的代碼,主線程繼續(xù)向下執(zhí)行,兩 個(gè)線程各自獨(dú)立運(yùn)行互不影響。

  • 當(dāng)run方法執(zhí)行完畢后子線程結(jié)束,當(dāng)main方法執(zhí)行完畢后主線程結(jié)束。

  • 兩個(gè)線程執(zhí)行沒(méi)有明確的先后執(zhí)行次序,由操作系統(tǒng)調(diào)度算法來(lái)決定。

方式的比較

繼承Thread類(lèi)的方式代碼簡(jiǎn)單,但是若該類(lèi)繼承Thread類(lèi)后則無(wú)法繼承其它類(lèi),而實(shí)現(xiàn) Runnable接口的方式代碼復(fù)雜,但不影響該類(lèi)繼承其它類(lèi)以及實(shí)現(xiàn)其它接口,因此以后的開(kāi)發(fā)中 推薦使用第二種方式。

匿名內(nèi)部類(lèi)的方式

使用匿名內(nèi)部類(lèi)的方式來(lái)創(chuàng)建和啟動(dòng)線程。

線程的生命周期(熟悉)

微信圖片_20210408205232.png
  • 新建狀態(tài) - 使用new關(guān)鍵字創(chuàng)建之后進(jìn)入的狀態(tài),此時(shí)線程并沒(méi)有開(kāi)始執(zhí)行。

  • 就緒狀態(tài) - 調(diào)用start方法后進(jìn)入的狀態(tài),此時(shí)線程還是沒(méi)有開(kāi)始執(zhí)行。

  • 運(yùn)行狀態(tài) - 使用線程調(diào)度器調(diào)用該線程后進(jìn)入的狀態(tài),此時(shí)線程開(kāi)始執(zhí)行,當(dāng)線程的時(shí)間片執(zhí)行完 畢后任務(wù)沒(méi)有完成時(shí)回到就緒狀態(tài)。

  • 消亡狀態(tài) - 當(dāng)線程的任務(wù)執(zhí)行完成后進(jìn)入的狀態(tài),此時(shí)線程已經(jīng)終止。

  • 阻塞狀態(tài) - 當(dāng)線程執(zhí)行的過(guò)程中發(fā)生了阻塞事件進(jìn)入的狀態(tài),如:sleep方法。 阻塞狀態(tài)解除后進(jìn)入就緒狀態(tài)。

線程的編號(hào)和名稱(chēng)(熟悉)

方法聲明 功能介紹
long getId() 獲取調(diào)用對(duì)象所表示線程的編號(hào)
String getName() 獲取調(diào)用對(duì)象所表示線程的名稱(chēng)
void setName(String name) 設(shè)置/修改線程的名稱(chēng)為參數(shù)指定的數(shù)值
static Thread currentThread() 獲取當(dāng)前正在執(zhí)行線程的引用

常用的方法(重點(diǎn))

方法聲明 功能介紹
static void yield() 當(dāng)前線程讓出處理器(離開(kāi)Running狀態(tài)),使當(dāng)前線程進(jìn)入Runnable 狀態(tài)等待
static void sleep(times) 使當(dāng)前線程從 Running 放棄處理器進(jìn)入Block狀態(tài), 休眠times毫秒, 再返 回到Runnable如果其他線程打斷當(dāng)前線程的Block(sleep), 就會(huì)發(fā)生 InterruptedException。重寫(xiě)run方法不能throws(拋出異常),因?yàn)樽宇?lèi)不能拋出更大的異常
int getPriority() 獲取線程的優(yōu)先級(jí),默認(rèn)為5,MIN_PRIORITY = 1;NORM_PRIORITY = 5;MAX_PRIORITY = 10;
void setPriority(int newPriority) 修改線程的優(yōu)先級(jí)。 優(yōu)先級(jí)越高的線程不一定先執(zhí)行,但該線程獲取到時(shí)間片的機(jī)會(huì)會(huì)更多 一些
void join() 等待該線程終止,當(dāng)前正在執(zhí)行的線程對(duì)象等待調(diào)用線程對(duì)象,也就是主線程等待子線程終止
void join(long millis) 等待參數(shù)指定的毫秒數(shù)
boolean isDaemon() 用于判斷是否為守護(hù)線程 ,默認(rèn)false,當(dāng)子線程是守護(hù)線程,當(dāng)主線程結(jié)束后,子線程隨之結(jié)束
void setDaemon(boolean on) 用于設(shè)置線程為守護(hù)線程,必須在start 之前調(diào)用

線程同步機(jī)制(重點(diǎn))

基本概念
  • 當(dāng)多個(gè)線程同時(shí)訪問(wèn)同一種共享資源時(shí),可能會(huì)造成數(shù)據(jù)的覆蓋等不一致性問(wèn)題,此時(shí)就需要對(duì)線 程之間進(jìn)行通信和協(xié)調(diào),該機(jī)制就叫做線程的同步機(jī)制。

  • 多個(gè)線程并發(fā)讀寫(xiě)同一個(gè)臨界資源時(shí)會(huì)發(fā)生線程并發(fā)安全問(wèn)題。

  • 異步操作:多線程并發(fā)的操作,各自獨(dú)立運(yùn)行。

  • 同步操作:多線程串行的操作,先后執(zhí)行的順序。

解決方案
  • 由程序結(jié)果可知:當(dāng)兩個(gè)線程同時(shí)對(duì)同一個(gè)賬戶(hù)進(jìn)行取款時(shí),導(dǎo)致最終的賬戶(hù)余額不合理。

  • 引發(fā)原因:線程一執(zhí)行取款時(shí)還沒(méi)來(lái)得及將取款后的余額寫(xiě)入后臺(tái),線程二就已經(jīng)開(kāi)始取款。

  • 解決方案:讓線程一執(zhí)行完畢取款操作后,再讓線程二執(zhí)行即可,將線程的并發(fā)操作改為串行操 作。

  • 經(jīng)驗(yàn)分享:在以后的開(kāi)發(fā)盡量減少串行操作的范圍,從而提高效率。

實(shí)現(xiàn)方式
  • 在Java語(yǔ)言中使用synchronized關(guān)鍵字來(lái)實(shí)現(xiàn)同步/對(duì)象鎖機(jī)制從而保證線程執(zhí)行的原子性,具體 方式如下:

  • 使用同步代碼塊的方式實(shí)現(xiàn)部分代碼的鎖定,格式如下: synchronized(類(lèi)類(lèi)型的引用) { 編寫(xiě)所有需要鎖定的代碼; }

  • 使用同步方法的方式實(shí)現(xiàn)所有代碼的鎖定。 直接使用synchronized關(guān)鍵字來(lái)修飾整個(gè)方法即可 該方式等價(jià)于: synchronized(this) { 整個(gè)方法體的代碼 }

靜態(tài)方法的鎖定
  • 當(dāng)我們對(duì)一個(gè)靜態(tài)方法加鎖,如: public synchronized static void xxx(){….}

  • 那么該方法鎖的對(duì)象是類(lèi)對(duì)象。每個(gè)類(lèi)都有唯一的一個(gè)類(lèi)對(duì)象。獲取類(lèi)對(duì)象的方式:類(lèi)名.class。

  • 靜態(tài)方法與非靜態(tài)方法同時(shí)使用了synchronized后它們之間是非互斥關(guān)系的。

  • 原因在于:靜態(tài)方法鎖的是類(lèi)對(duì)象而非靜態(tài)方法鎖的是當(dāng)前方法所屬對(duì)象。

注意事項(xiàng)

使用synchronized保證線程同步應(yīng)當(dāng)注意:

  • 多個(gè)需要同步的線程在訪問(wèn)同步塊時(shí),看到的應(yīng)該是同一個(gè)鎖對(duì)象引用。

  • 在使用同步塊時(shí)應(yīng)當(dāng)盡量減少同步范圍以提高并發(fā)的執(zhí)行效率。

線程安全類(lèi)和不安全類(lèi)
  • StringBuffer類(lèi)是線程安全的類(lèi),但StringBuilder類(lèi)不是線程安全的類(lèi)。

  • Vector類(lèi)和 Hashtable類(lèi)是線程安全的類(lèi),但ArrayList類(lèi)和HashMap類(lèi)不是線程安全的類(lèi)。

  • Collections.synchronizedList() 和 Collections.synchronizedMap()等方法實(shí)現(xiàn)安全。

死鎖的概念
//線程一執(zhí)行的代碼:
public void run(){
 synchronized(a){ //持有對(duì)象鎖a,等待對(duì)象鎖b
 synchronized(b){
 編寫(xiě)鎖定的代碼;
 }
 }
}
//線程二執(zhí)行的代碼:
public void run(){
 synchronized(b){ //持有對(duì)象鎖b,等待對(duì)象鎖a
 synchronized(a){
 編寫(xiě)鎖定的代碼;
 }
 }
}

注意: 在以后的開(kāi)發(fā)中盡量減少同步的資源,減少同步代碼塊的嵌套結(jié)構(gòu)的使用!

使用Lock(鎖)實(shí)現(xiàn)線程同步
基本概念
  • 從Java5開(kāi)始提供了更強(qiáng)大的線程同步機(jī)制—使用顯式定義的同步鎖對(duì)象來(lái)實(shí)現(xiàn)。

  • java.util.concurrent.locks.Lock接口是控制多個(gè)線程對(duì)共享資源進(jìn)行訪問(wèn)的工具。

  • 該接口的主要實(shí)現(xiàn)類(lèi)是ReentrantLock類(lèi),該類(lèi)擁有與synchronized相同的并發(fā)性,在以后的線程 安全控制中,經(jīng)常使用ReentrantLock類(lèi)顯式加鎖和釋放鎖。

常用的方法
方法聲明 功能介紹
ReentrantLock() 使用無(wú)參方式構(gòu)造對(duì)象
void lock() 獲取鎖
void unlock() 釋放鎖
與synchronized方式的比較
  • Lock是顯式鎖,需要手動(dòng)實(shí)現(xiàn)開(kāi)啟和關(guān)閉操作,而synchronized是隱式鎖,執(zhí)行鎖定代碼后自動(dòng) 釋放。

  • Lock只有同步代碼塊方式的鎖,而synchronized有同步代碼塊方式和同步方法兩種鎖。

  • 使用Lock鎖方式時(shí),Java虛擬機(jī)將花費(fèi)較少的時(shí)間來(lái)調(diào)度線程,因此性能更好。

Object類(lèi)常用的方法
方法聲明 功能介紹
void wait() 用于使得線程進(jìn)入等待狀態(tài),直到其它線程調(diào)用notify()或notifyAll()方 法(當(dāng)前線程進(jìn)入阻塞狀態(tài),自動(dòng)釋放對(duì)象鎖,必須在鎖定的代碼中使用)
void wait(long timeout) 用于進(jìn)入等待狀態(tài),直到其它線程調(diào)用方法或參數(shù)指定的毫秒數(shù)已經(jīng)過(guò) 去為止
void notify() 用于喚醒等待的單個(gè)線程
void notifyAll() 用于喚醒等待的所有線程
線程池(熟悉)
實(shí)現(xiàn)Callable接口

從Java5開(kāi)始新增加創(chuàng)建線程的第三種方式為實(shí)現(xiàn)java.util.concurrent.Callable接口。

常用的方法如下:V call() 計(jì)算結(jié)果并返回

FutureTask類(lèi)

java.util.concurrent.FutureTask類(lèi)用于描述可取消的異步計(jì)算,該類(lèi)提供了Future接口的基本實(shí) 現(xiàn),包括啟動(dòng)和取消計(jì)算、查詢(xún)計(jì)算是否完成以及檢索計(jì)算結(jié)果的方法,也可以用于獲取方法調(diào)用 后的返回結(jié)果。

常用的方法如下:

FutureTask(Callable callable) 根據(jù)參數(shù)指定的引用來(lái)創(chuàng)建一個(gè)未來(lái)任務(wù)

V get() 獲取call方法計(jì)算的結(jié)果

線程池的由來(lái)
  • 線程池的概念:首先創(chuàng)建一些線程,它們的集合稱(chēng)為線程池,當(dāng)服務(wù)器接受到一個(gè)客戶(hù)請(qǐng)求后,就 從線程池中取出一個(gè)空閑的線程為之服務(wù),服務(wù)完后不關(guān)閉該線程,而是將該線程還回到線程池 中。

  • 在線程池的編程模式下,任務(wù)是提交給整個(gè)線程池,而不是直接交給某個(gè)線程,線程池在拿到任務(wù) 后,它就在內(nèi)部找有無(wú)空閑的線程,再把任務(wù)交給內(nèi)部某個(gè)空閑的線程,任務(wù)是提交給整個(gè)線程 池,一個(gè)線程同時(shí)只能執(zhí)行一個(gè)任務(wù),但可以同時(shí)向一個(gè)線程池提交多個(gè)任務(wù)。

相關(guān)類(lèi)和方法
  • 從Java5開(kāi)始提供了線程池的相關(guān)類(lèi)和接口:java.util.concurrent.Executors類(lèi)和 java.util.concurrent.ExecutorService接口。

  • 其中Executors是個(gè)工具類(lèi)和線程池的工廠類(lèi),可以創(chuàng)建并返回不同類(lèi)型的線程池,常用方法如 下:

方法聲明 功能介紹
static ExecutorService newCachedThreadPool() 創(chuàng)建一個(gè)可根據(jù)需要?jiǎng)?chuàng)建新線程的 線程池
static ExecutorService newFixedThreadPool(int nThreads) 創(chuàng)建一個(gè)可重用固定線程數(shù)的線程 池
static ExecutorService newSingleThreadExecutor() 創(chuàng)建一個(gè)只有一個(gè)線程的線程池
  • 其中ExecutorService接口是真正的線程池接口,主要實(shí)現(xiàn)類(lèi)是ThreadPoolExecutor,常用方法 如下
方法聲明 功能介紹
void execute(Runnable command) 執(zhí)行任務(wù)和命令,通常用于執(zhí)行Runnable
Future submit(Callable task) 執(zhí)行任務(wù)和命令,通常用于執(zhí)行Callable
void shutdown() 啟動(dòng)有序關(guān)閉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容