來(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)線程。
線程的生命周期(熟悉)

新建狀態(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)閉 |