1碉纺、多線程基本概念
- 程序(program)是為了完成特定任務(wù),用某種語言編寫的一組指令的集合
- 進(jìn)程(process)是程序執(zhí)行的一次過程刻撒,或是正在運(yùn)行的程序骨田,是一個(gè)動(dòng)態(tài)的過程,有自己的生命周期
- 線程(thread)是程序內(nèi)部執(zhí)行的一條路徑声怔,每個(gè)線程擁有獨(dú)立的運(yùn)行棧态贤、計(jì)數(shù)器
- 多個(gè)線程共享一個(gè)進(jìn)程內(nèi)的內(nèi)存地址、堆數(shù)據(jù)
2醋火、并行與并發(fā)的區(qū)別
- 并行:多個(gè)cpu同時(shí)執(zhí)行多個(gè)任務(wù)悠汽,發(fā)生在某一刻
- 并發(fā):一個(gè)cpu同時(shí)執(zhí)行多個(gè)任務(wù),發(fā)生在某個(gè)時(shí)間段芥驳,如: 秒殺活動(dòng)
3柿冲、創(chuàng)建線程的方式
3.1 方式一:繼承Thread類
1、繼承Thread類兆旬,重寫父類的run()方法
2假抄、調(diào)用線程對象的start()方法,而不是run()方法
3.2 方式二:實(shí)現(xiàn)Runnable接口
1、實(shí)現(xiàn)Runnable接口慨亲,重寫接口的run()方法
2婚瓜、通過Thread類的構(gòu)造器創(chuàng)建多線程
3、將Runnable的實(shí)現(xiàn)類作為參數(shù)傳遞給Thread類的構(gòu)造器刑棵,創(chuàng)建Thread類的對象
4巴刻、調(diào)用Thread類的start()方法開啟線程
3.3 方式三:實(shí)現(xiàn)Callable接口
1、實(shí)現(xiàn)Callable接口蛉签,重寫call()方法
2胡陪、將Callable接口的實(shí)現(xiàn)類作為參數(shù),傳遞給FutureTask類的構(gòu)造器碍舍,創(chuàng)建FutureTask類的對象
3柠座、將FutureTask類作為參數(shù)傳遞給Thread類的構(gòu)造器,創(chuàng)建Thread類的對象
4片橡、調(diào)用Thread類的start()方法開啟線程
5妈经、較Runnable相比,具有返回值捧书、可拋出異常
6吹泡、獲取返回值,需借助FutureTask類的get()方法经瓷,在開啟start()方法之后調(diào)用
3.4 方式四:創(chuàng)建線程池
背景:經(jīng)常創(chuàng)建和銷毀爆哑,使用量特別大的資源,比如并發(fā)編程對性能影響很大
解決思路:提前創(chuàng)建好多個(gè)線程舆吮,放入線程池揭朝,使用時(shí)直接獲取,使用放回池中色冀,避免了頻繁創(chuàng)建和銷毀線程的開銷
注意事項(xiàng):
1潭袱、如果調(diào)用的run()方法,那就跟調(diào)用普通方法一樣呐伞,無法啟動(dòng)多線程
2敌卓、一個(gè)線程只能調(diào)用一次start()方法,多次調(diào)用將拋出異常 "IIIeglThreadStateException"
4伶氢、繼承方式和實(shí)現(xiàn)方式的區(qū)別
- 繼承Thread:線程代碼存放在Thread子類run()方法中
- 實(shí)現(xiàn)Runnable:線程代碼存放在接口的子類的run()方法中
- 實(shí)現(xiàn)Runnable方式,避免了單繼承的局限性
- 多個(gè)線程可以共享同一個(gè)接口實(shí)現(xiàn)類的對象(也叫共享數(shù)據(jù))瘪吏,適合多個(gè)線程來處理同一份資源
5癣防、多線程引發(fā)出來的連鎖思考
- 由于單線程不能很好的利用cpu資源,因此使用多線程來同時(shí)處理程序掌眠,加快響應(yīng)速度
- 使用多線程蕾盯,肯定會(huì)涉及到多個(gè)線程共同搶占一個(gè)資源的問題,會(huì)帶來線程不安全
- 解決線程不安全蓝丙,就要使用同步方式级遭,鎖定共享數(shù)據(jù)
- 即一次只能被一個(gè)線程使用望拖,使用完之后,釋放鎖讓下一個(gè)線程使用
6挫鸽、Thread常用方法
1说敏、start():啟動(dòng)當(dāng)前線程;調(diào)用當(dāng)前線程的run()
2丢郊、run(): 通常需要重寫Thread類中的此方法盔沫,將創(chuàng)建的線程要執(zhí)行的操作聲明在此方法中
3、currentThread():靜態(tài)方法枫匾,返回執(zhí)行當(dāng)前代碼的線程
4架诞、getName():獲取當(dāng)前線程的名字
5、setName():設(shè)置當(dāng)前線程的名字
6干茉、yield():釋放當(dāng)前cpu的執(zhí)行權(quán)
7谴忧、join():在線程a中調(diào)用線程b的join(),此時(shí)線程a就進(jìn)入阻塞狀態(tài),直到線程b完全執(zhí)行完以后角虫,線程a才結(jié)束阻塞狀態(tài)沾谓。
8、stop():已過時(shí)上遥。當(dāng)執(zhí)行此方法時(shí)搏屑,強(qiáng)制結(jié)束當(dāng)前線程。
9粉楚、sleep(long millitime):讓當(dāng)前線程“睡眠”指定的millitime毫秒辣恋。在指定的millitime毫秒時(shí)間內(nèi),當(dāng)前線程是阻塞狀態(tài)模软。
10伟骨、isAlive():判斷當(dāng)前線程是否存活
7、 線程優(yōu)先級
- MAX_PRIORITY:10
- MIN _PRIORITY:1
- NORM_PRIORITY:5 -->默認(rèn)優(yōu)先級
- getPriority():獲取線程的優(yōu)先級
- setPriority(int p):設(shè)置線程的優(yōu)先級
高優(yōu)先級的線程要搶占低優(yōu)先級線程cpu的執(zhí)行權(quán)燃异。但是只是從概率上講携狭,高優(yōu)先級的線程高概率的情況下
被執(zhí)行。并不意味著只有當(dāng)高優(yōu)先級的線程執(zhí)行完以后回俐,低優(yōu)先級的線程才執(zhí)行逛腿。
7、線程的生命周期
1仅颇、新建:當(dāng)一個(gè)Thread類或其子類的對象被實(shí)例化单默,新生的線程處理新建狀態(tài)
2、就緒:當(dāng)新建的線程被start()后忘瓦,線程處理就緒搁廓,等待著cpu時(shí)間片,具備運(yùn)行條件
3、運(yùn)行:當(dāng)就緒的線程分配到cpu資源時(shí)境蜕,便進(jìn)入了運(yùn)行狀態(tài)蝙场,調(diào)用run()方法,執(zhí)行業(yè)務(wù)邏輯
4粱年、阻塞:當(dāng)被人為掛起或執(zhí)行輸入輸出操作時(shí)售滤,讓出cpu時(shí)間片并臨時(shí)終止自己的執(zhí)行,進(jìn)入阻塞狀態(tài)
5逼泣、死亡:線程完成工作后趴泌、線程被強(qiáng)制退出、出現(xiàn)異常拉庶,都會(huì)導(dǎo)致線程結(jié)束嗜憔,進(jìn)入死亡狀態(tài)
8、線程同步
8.1氏仗、出現(xiàn)的問題
當(dāng)多個(gè)線程同時(shí)操作同一份共享數(shù)據(jù)時(shí)吉捶,一個(gè)線程執(zhí)行了一部分,另一個(gè)線程參與進(jìn)來皆尔,會(huì)造成共享數(shù)據(jù)的不完整性
8.2呐舔、如何解決線程共享帶來的問題
給共享數(shù)據(jù)加鎖(Synchronized),一次只能被一個(gè)線程訪問慷蠕,當(dāng)線程操作完共享數(shù)據(jù)時(shí)珊拼,釋放鎖
8.3、線程同步機(jī)制
1流炕、必須確保使用同一資源的多個(gè)線程共用一把鎖澎现,否則無法保證線程安全
2、 如果使用Runnable方式實(shí)現(xiàn)的多線程每辟,用this關(guān)鍵字剑辫,可充當(dāng)同步監(jiān)視器
3、如果使用Thread繼承方式實(shí)現(xiàn)的多線程渠欺,請用當(dāng)前類.class或者靜態(tài)對象妹蔽,充當(dāng)同步監(jiān)視器,如果使用this關(guān)鍵字的話挠将,即每個(gè)線程實(shí)例對象同時(shí)擁有自己的共享數(shù)據(jù)
8.3.1胳岂、同步代碼塊
- 操作共享數(shù)據(jù)的代碼,即為需要被同步的代碼
- 同步監(jiān)視器舔稀,俗稱鎖旦万,任何一個(gè)對象可以充當(dāng)鎖,多個(gè)線程必須使用同一把鎖镶蹋,否則無效
synchronized (this){
}
8.3.2、同步方法
- Runnable實(shí)現(xiàn)方式
private synchronized void show(){//同步監(jiān)視器:this
}
- Thread繼承方式
private static synchronized void show(){//同步監(jiān)視器:即為當(dāng)前類.calss
}
8.3.3、Lock(鎖)
- 從JDK5.0開始贺归,Java提供Lock線程同步機(jī)制淆两,通過顯示定義同步鎖Lock對象來實(shí)現(xiàn)同步機(jī)制
- 訪問共享資源之前,應(yīng)先取得Lock對象拂酣,在之前手動(dòng)開啟lock鎖秋冰,訪問共享資源之后,手動(dòng)關(guān)閉lock鎖
- ReentrantLock類實(shí)現(xiàn)的Lock接口婶熬,它與synchronized一樣剑勾,都是對共享資源同步機(jī)制的訪問
8.3.4、synchronized與Lock的區(qū)別
synchronized:
1赵颅、隱式鎖出了作用域自動(dòng)釋放
2虽另、具有同步代碼塊鎖和方法鎖,2種鎖方式
Lock:
1饺谬、顯示鎖(需手動(dòng)開啟和關(guān)閉鎖)
2捂刺、只有代碼塊鎖,沒有方法鎖
3募寨、JVM將花費(fèi)更少時(shí)間調(diào)度線程族展,性能會(huì)更好些
9、線程的死鎖
9.1拔鹰、死鎖原因
1仪缸、不同的線程分別占用對方需要的同步資源不放棄,都在等待對方釋放自己需要的同步資源列肢,就形成了死鎖
2恰画、出現(xiàn)死鎖后,并不會(huì)拋出異常和提示信息例书,所有線程處于阻塞狀態(tài)锣尉,無法繼續(xù)執(zhí)行操作
9.2、解決方案
1决采、盡量減少同步資源的定義
2自沧、盡量避免使用嵌套同步
10、線程的通信
線程與線程之間難免會(huì)相互協(xié)作去完成一件較復(fù)雜的功能树瞭,典型的例子生產(chǎn)者-消費(fèi)者
- wait():一旦執(zhí)行此方法拇厢,當(dāng)前線程就進(jìn)入阻塞狀態(tài),并釋放同步監(jiān)視器
- notify():一旦執(zhí)行此方法晒喷,就會(huì)喚醒被wait的一個(gè)線程孝偎。如果有多個(gè)線程被wait,就喚醒優(yōu)先級高的那個(gè)
- notifyAll():一旦執(zhí)行此方法凉敲,就會(huì)喚醒所有被wait的線程
1衣盾、wait()寺旺,notify(),notifyAll()三個(gè)方法必須使用在同步代碼塊或同步方法中
2势决、wait()阻塑,notify(),notifyAll()三個(gè)方法的調(diào)用者必須是同步代碼塊或同步方法中的同步監(jiān)視器果复,否則會(huì)出現(xiàn)IllegalMonitorStateException異常
3陈莽、wait(),notify()虽抄,notifyAll()三個(gè)方法是定義在java.lang.Object類中