软件构造学习笔记-第十四周、十五周
課程進(jìn)入了尾聲。本周內(nèi)容主要是線程安全相關(guān)。線程錯(cuò)誤比一般的錯(cuò)誤更加難以發(fā)現(xiàn)和修改,甚至加入一條print語句就能改變時(shí)間分片,從而導(dǎo)致錯(cuò)誤消失。重點(diǎn)介紹了“鎖”的機(jī)制,在使用時(shí)避免對(duì)整個(gè)方法進(jìn)行l(wèi)ock,而是對(duì)可能發(fā)生線程不安全的指令進(jìn)行l(wèi)ock操作,以免程序性能受到明顯影響。同時(shí)避免“死鎖”現(xiàn)象發(fā)生,在使用多個(gè)lock時(shí)注意順序。
并發(fā)
1.并行:將程序布置在多個(gè)CPU上執(zhí)行。
并發(fā):將任務(wù)拆分為多個(gè)階段,在同一個(gè)CPU上執(zhí)行(切片)。
2.并發(fā)的兩個(gè)模型:共享內(nèi)存(只能用于線程)和消息傳遞(線程、進(jìn)程)。
進(jìn)程和線程
1.進(jìn)程和線程都是并發(fā)模塊的類型。進(jìn)程比較“重量級(jí)”,私有空間,彼此隔離;線程“輕量級(jí)”,是程序內(nèi)部的控制機(jī)制。一個(gè)進(jìn)程可以形成多個(gè)線程。
2.每個(gè)應(yīng)用至少有一個(gè)線程,主線程可以創(chuàng)建其它的線程。
3.創(chuàng)建線程的方法:繼承Thread;從Runnable接口構(gòu)造Thread對(duì)象。
創(chuàng)建線程需要調(diào)用Thread類的start方法,不能運(yùn)行具體的run方法
交錯(cuò)(Interleaving)和競爭
1.時(shí)間分片:雖然有多個(gè)線程,但只有一個(gè)CPU,每個(gè)時(shí)刻只能執(zhí)行一個(gè)線程。通過時(shí)間分片在多個(gè)進(jìn)程/線程間共享處理器。由操作系統(tǒng)自動(dòng)調(diào)度。
2.共享內(nèi)存時(shí)的競爭情況
不恰當(dāng)?shù)姆制僮鲿?huì)導(dǎo)致錯(cuò)誤。右側(cè)A和B發(fā)生了競爭/線程干擾。
共享內(nèi)存多線程的運(yùn)行結(jié)果實(shí)例,每條語句可劃分為讀取-計(jì)算-寫回三個(gè)原子操作
3.消息傳遞時(shí)的競爭情況
由于時(shí)間分片,消息傳遞機(jī)制也無法解決競爭問題。
4.調(diào)用方法主動(dòng)影響交錯(cuò)現(xiàn)象
①Thread.sleep(time) 線程休眠time毫秒
②Thread.interrupt() 向線程發(fā)出中斷信號(hào),例如t.interrupt(),即在其它線程里向t發(fā)出中斷信號(hào)。收到中斷信號(hào)后t不一定中斷,由t本身決定。正常運(yùn)行期間,即使收到中斷信號(hào)也不會(huì)理會(huì);在休眠時(shí)收到中斷信號(hào)則拋出異常。
③Thread.join() 讓線程保持執(zhí)行,直到其執(zhí)行結(jié)束。執(zhí)行該操作時(shí),也會(huì)檢測其它線程發(fā)來的中斷信號(hào)。
t1收到中斷信號(hào)時(shí),如果一次循環(huán)都未執(zhí)行,則先輸入if再輸出else;如果執(zhí)行一次,則先輸出else再輸出if;如果已經(jīng)進(jìn)行兩次循環(huán)則輸出兩個(gè)else。此外t1和t2的start執(zhí)行先后不定。
保證線程安全的策略
1.Confinement
要求類中方法不能訪問屬性(過于嚴(yán)格)。
2.Immutability
使用不可變數(shù)據(jù)類型和不可變引用,避免多個(gè)線程之間的競爭。如果是有益可變性,則需要通過“加鎖”保證線程安全。
3.使用線程安全數(shù)據(jù)類型
如果必須使用mutable數(shù)據(jù)類型在多個(gè)線程之間共享數(shù)據(jù),要使用線程安全的數(shù)據(jù)類型。一般來說JDK為ADT額外提供一個(gè)線程安全的類,但性能受影響。List、Map、Set都是線程不安全的,API提供集合類的包裝,對(duì)每個(gè)操作都以原子操作進(jìn)行。執(zhí)行其上的某個(gè)操作是線程安全的,但如果多個(gè)操作放在一起,仍然不安全。//即使在線程安全的集合類上,使用iterator也是不安全的。除非使用lock機(jī)制。
4.鎖和同步
①程序員負(fù)責(zé)多線程之間對(duì)mutable數(shù)據(jù)的共享操作,通過同步策略避免多個(gè)線程同時(shí)訪問數(shù)據(jù)。使用鎖機(jī)制,獲得對(duì)數(shù)據(jù)的獨(dú)家更改權(quán),其它線程的訪問被阻塞。
②Lock是Java的內(nèi)嵌機(jī)制,每個(gè)對(duì)象都有相關(guān)聯(lián)的lock。
③Lock用以保護(hù)共享數(shù)據(jù)。要實(shí)現(xiàn)互斥,則必須使用同一個(gè)lock進(jìn)行保護(hù)。
④構(gòu)造方法默認(rèn)互斥,不需加鎖。除非必要,否則不加鎖;如果使用,要盡量縮小范圍。因?yàn)闀?huì)給性能帶來極大影響。
⑤對(duì)靜態(tài)方法進(jìn)行加鎖,會(huì)同時(shí)鎖住類的所有對(duì)象。
⑥1和4正確。A獲得了list的鎖并不意味著其它對(duì)象不能獲取/改變list的元素。對(duì)同一個(gè)mutable對(duì)象的操作,必須在各線程里用synchronized全部保護(hù)起來。對(duì)于該例子即是將B的兩條語句鎖住。
⑦3和4需要鎖住。
⑧使用lock的條件:任何共享的mutable變量/對(duì)象在被讀/寫的時(shí)候必須加鎖。涉及到多個(gè)mutable變量的時(shí)候,它們必須被同一個(gè)lock保護(hù)(例如開始和結(jié)束時(shí)間)。
死鎖
1.多個(gè)線程競爭lock,相互等待對(duì)方釋放lock(必須滿足多個(gè)線程使用多個(gè)鎖、訪問順序不同)。
2.避免方法:多個(gè)線程使用同一順序的lock;用單個(gè)lock保護(hù)多個(gè)對(duì)象(粗粒度)。
wait(), notify(), notifyAll()
都是object類的方法。需要在synchronized塊中調(diào)用,要求調(diào)用對(duì)象和lock的對(duì)象相同。
o.wait(): 釋放o的鎖(阻塞當(dāng)前線程),進(jìn)入到o的等待隊(duì)列中。
o.notify(): 喚醒等待隊(duì)列的一個(gè)線程。
o.notifyAll(): 喚醒等待隊(duì)列的所有線程。
總結(jié)
以上是生活随笔為你收集整理的软件构造学习笔记-第十四周、十五周的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大麦片的功效与作用、禁忌和食用方法
- 下一篇: 吴恩达DeepLearningCours