什麼是併發編程?
進程,線程和時間片
交織和競爭條件
線程安全前端
如何作安全論證
總結java
併發
併發性:多個計算同時發生。程序員
在現代編程中無處不在:數據庫
併發在現代編程中相當重要:編程
爲何要「併發」?設計模式
處理器時鐘速度再也不增長。摩爾定律失效了
相反,咱們每一個新一代芯片都會得到更多內核。 「核」變得愈來愈多
爲了讓計算更快運行,咱們必須將計算分解爲併發塊。爲了充分利用多核和多處理器,須要將程序轉化爲並行執行數組
並行編程的兩種模型瀏覽器
共享內存:併發模塊經過在內存中讀寫共享對象進行交互。
共享內存:在內存中讀寫共享數據
消息傳遞:併發模塊經過通訊通道相互發送消息進行交互。模塊發送消息,並將傳入的消息發送到每一個模塊以便處理。
消息傳遞:經過信道(channel)交換消息緩存
共享內存安全
共享內存模型的示例:
兩個處理器,共享內存
同一臺機器上的兩個程序,共享文件系統
同一個Java的程序內的兩個線程,共享的Java對象
消息傳遞
消息傳遞的例子:
網絡上的兩臺計算機,經過網絡鏈接通信
瀏覽器和Web服務器,A請求頁面,B發送頁面數據給A
即時通信軟件的客戶端和服務器
同一臺計算機上的兩個程序,經過管道鏈接進行通信
進程和線程
消息傳遞和共享內存模型是關於併發模塊如何通訊的。
併發模塊自己有兩種不一樣的類型:進程和線程,兩個基本的執行單元。
併發模塊的類型:進程和線程
(1)進程
進程抽象是一個虛擬計算機(一個獨立的執行環境,具備一套完整的私有基本運行時資源,尤爲是內存)。進程:擁有整臺計算機的資源
就像鏈接網絡的計算機同樣,進程一般在它們之間不共享內存。多進程之間不共享內存
進程一般被視爲與程序或應用程序的同義詞。通常來講,進程==程序==應用
爲了促進進程之間的通訊,大多數操做系統都支持進程間通訊(IPC)資源,例如管道和套接字。 OS支持的IPC機制(pipe / socket)支持進程間通訊
Java虛擬機的大多數實現都是做爲單個進程運行的。可是Java應用程序可使用ProcessBuilder對象建立其餘進程。 JVM一般運行單一進程,但也能夠建立新的進程。
(2)線程
線程和多線程編程
就像一個進程表明一個虛擬計算機同樣,線程抽象表明一個虛擬處理器,線程有時稱爲輕量級進程 進程=虛擬機;線程=虛擬CPU
線程自動準備好共享內存,由於線程共享進程中的全部內存。共享內存
線程與進程
線程是輕量級的 進程是重量級的
線程共享內存空間 進程有本身的
線程須要同步(當調用可變對象時線程保有鎖) 進程不須要
殺死線程是不安全的 殺死進程是安全的
多線程執行是Java平臺的基本功能。
兩種建立線程的方法:
如何建立一個線程:子類Thread
子類Thread
調用Thread.start()以啓動新線程。
建立線程的方法:提供一個Runnable對象
提供一個Runnable對象
如何建立線程
一個很是常見的習慣用法是用一個匿名的Runnable啓動一個線程,它消除了命名的類:
Runnable接口表示要由線程完成的工做。
爲何使用線程?
面對阻塞活動的表現
在多處理器上的性能
乾淨地處理天然併發
在Java中,線程是生活中的事實
咱們都是併發程序員......
爲了利用咱們的多核處理器,咱們必須編寫多線程代碼
好消息:它不少都是爲你寫的
壞消息:你仍然必須瞭解基本面
交錯和競爭
在具備單個執行核心的計算機系統中,在任何給定時刻只有一個線程正在執行。雖然有多線程,但只有一個核,每一個時刻只能執行一個線程
今天的計算機系統具備多個處理器或具備多個執行核心的處理器。那麼,個人計算機中只有一個或兩個處理器的多個併發線程如何處理?即便是多核CPU,進程/線程的數目也每每大於核的數目
時間分片的一個例子
三個線程T1,T2和T3可能在具備兩個實際處理器的機器上進行時間分割。
在大多數系統中,時間片發生不可預知的和非肯定性的,這意味着線程可能隨時暫停或恢復。時間分片是由操做系統自動調度的
共享內存示例
線程之間的共享內存可能會致使微妙的錯誤!
例如:一家銀行擁有使用共享內存模式的取款機,所以全部取款機均可以在內存中讀取和寫入相同的帳戶對象。
將銀行簡化爲一個帳戶,在餘額變量中存儲美圓餘額,以及兩個操做存款和取款,只需添加或刪除美圓便可:
客戶使用現金機器進行以下交易:
每筆交易只是一美圓存款,而後是基礎提款,因此它應該保持帳戶餘額不變。
在這一天結束時,不管有多少現鈔機在運行,或者咱們處理了多少交易,咱們都應該預期賬戶餘額仍然爲0.按理說,餘額應該始終爲0
交錯
假設兩臺取款機A和B同時在存款上工做。
如下是deposit()步驟一般如何分解爲低級處理器指令的方法:
當A和B同時運行時,這些低級指令彼此交錯...
餘額如今是1
競爭條件:程序的正確性(後置條件和不變量的知足)取決於併發計算A和B中事件的相對時間。發生這種狀況時,咱們說「A與B競爭」。
事件的一些交織多是能夠的,由於它們與單個非併發進程會產生什麼一致,可是其餘交織會產生錯誤的答案 - 違反後置條件或不變量。
調整代碼將無濟於事
全部這些版本的銀行帳戶代碼都具備相同的競爭條件!
你不能僅僅從Java代碼中看出處理器將如何執行它。
你不能說出原子操做是什麼。
Java編譯器不會對您的代碼生成的低級操做作出任何承諾。是否原子,由JVM肯定
競爭條件
關鍵的教訓是,你沒法經過觀察一個表達來判斷它是否會在競爭條件下安全。
競爭條件也被稱爲「線程干擾」
如今不只是自動取款機模塊,並且帳戶也是模塊。
模塊經過相互發送消息進行交互。
消息傳遞可否解決競爭條件?
不幸的是,消息傳遞並不能消除競爭條件的可能性。消息傳遞機制也沒法解決競爭條件問題
問題是再次交錯,可是此次將消息交給銀行帳戶,而不是A和B所執行的指令。仍然存在消息傳遞時間上的交錯
若是帳戶以一美圓開始,那麼什麼交錯的信息會欺騙A和B,使他們認爲他們既能夠提取一美圓,從而透支帳戶?
使用測試發現競爭條件很是困難。很難測試和調試由於競爭條件致使的錯誤
併發性錯誤表現出不好的重現性。由於交錯的存在,致使很難復現錯誤
Heisenbugs和Bohrbugs
一個heisenbug是一個軟件錯誤,當一我的試圖研究它時,它彷佛消失或改變了它的行爲。
順序編程中幾乎全部的錯誤都是bohrbugs。
併發性很難測試和調試!
當您嘗試用println或調試器查看heisenbug時,甚至可能會消失!增長打印語句甚至致使這種錯誤消失!〜
所以,將一個簡單的打印語句插入到cashMachine()中:
...忽然間,平衡老是0,而且錯誤彷佛消失了。但它只是被掩蓋了,並無真正固定。
3.5干擾線程自動交錯的一些操做
Thread.sleep()方法
使用Thread.sleep(time)暫停執行:致使當前線程暫停指定時間段的執行。線程的休眠
Thread.interrupt()方法
一個線程經過調用Thread對象上的中斷來發送一箇中斷,以便使用interrupt()方法中斷的線程 向線程發出中斷信號
要檢查線程是否中斷,請使用isInterrupted()方法。檢查線程是否被中斷
中斷表示線程應該中止正在執行的操做並執行其餘操做。 當某個線程被中斷後,通常來講應中止其run()中的執行,取決於程序員在run()中處理
可是,線程收到其餘線程發來的中斷信號,並不意味着必定要「中止」...
Thread.yield()方法
這種靜態方法主要用於通知系統當前線程願意「放棄CPU」一段時間。使用該方法,線程告知調度器:我能夠放棄CPU的佔用權,從而可能引發調度器喚醒其餘線程。
這是線程編程中不多使用的方法,由於調度應該由調度程序負責。儘可能避免在代碼中使用
Thread.join()方法
join()方法用於保存當前正在運行的線程的執行,直到指定的線程死亡(執行完畢)。讓當前線程保持執行,直到其執行結束
併發性:同時運行多個計算
共享內存和消息傳遞參數
進程和線程
競爭條件
這些想法主要以糟糕的方式與咱們的優秀軟件的關鍵屬性相關聯。
併發是必要的,但它會致使嚴重的正確性問題:
競爭條件:多個線程共享相同的可變變量,但不協調他們正在作的事情。
這是不安全的,由於程序的正確性可能取決於其低級操做時間的事故。
線程之間的「競爭條件」:做用於同一個可變數據上的多線程,彼此之間存在對該數據的訪問競爭並致使交錯,致使postcondition可能被違反,這是不安全的。
線程安全的意思
若是數據類型或靜態方法在從多個線程使用時行爲正確,則不管這些線程如何執行,都無需線程安全,也不須要調用代碼進行額外協調。線程安全:ADT或方法在多線程中要執行正確
如何捕捉這個想法?
還記得迭代器嗎?這不是線程安全的。
迭代器的規範說,你不能在迭代它的同時修改一個集合。
這是一個與調用程序相關的與時間有關的前提條件,若是違反它,Iterator不保證行爲正確。
線程安全意味着什麼:remove()的規範
做爲這種非本地契約現象的一個症狀,考慮Java集合類,這些類一般記錄在客戶端和實現者之間的很是明確的契約中。
線程安全的四種方法
監禁數據共享。不要在線程之間共享變量。
共享不可變數據。使共享數據不可變。
線程安全數據類型共享線程安全的可變數據。將共享數據封裝在爲您協調的現有線程安全數據類型中。
同步 同步機制共享共享線程不安全的可變數據,對外即爲線程安全的ADT。使用同步來防止線程同時訪問變量。同步是您構建本身的線程安全數據類型所需的。
不要共享:在單獨的線程中隔離可變狀態
不要改變:只共享不可變的狀態
若是必須共享可變狀態,請使用線程安全數據類型或同步
線程監禁是一個簡單的想法:
因爲共享可變數據是競爭條件的根本緣由,監禁經過不共享可變數據來解決。核心思想:線程之間不共享可變數據類型
局部變量老是受到線程監禁。局部變量存儲在堆棧中,每一個線程都有本身的堆棧。一次運行的方法可能會有多個調用,但每一個調用都有本身的變量專用副本,所以變量自己受到監禁。
避免全局變量
這個類在getInstance()方法中有一個競爭
假設兩個線程正在運行getInstance()。
對於兩個線程正在執行的每對可能的行號,是否有可能違反不變量?
全局靜態變量不會自動受到線程監禁。
更好的是,你應該徹底消除靜態變量。
isPrime()方法從多個線程調用並不安全,其客戶端甚至可能不會意識到它。
實現線程安全的第二種方法是使用不可變引用和數據類型。使用不可變數據類型和不可變引用,避免多線程之間的競爭條件
final變量是不可變的引用,因此聲明爲final的變量能夠安全地從多個線程訪問。
不可變對象一般也是線程安全的。不可變數據一般是線程安全的
咱們說「一般」,由於不可變性的當前定義對於併發編程而言過於鬆散。
對於併發性,這種隱藏的變異是不安全的。
更強的不變性定義
爲了確信一個不可變的數據類型是沒有鎖的線程安全的,咱們須要更強的不變性定義:
若是你遵循這些規則,那麼你能夠確信你的不可變類型也是線程安全的。
不要提供「setter」方法 - 修改字段引用的字段或對象的方法。
使全部字段最終和私有。
不要讓子類重寫方法。
若是實例字段包含對可變對象的引用,請不要容許更改這些對象:
實現線程安全的第三個主要策略是將共享可變數據存儲在現有的線程數據類型中。 若是必需要用mutable的數據類型在多線程之間共享數據,則要使用線程安全的數據類型。
通常來講,JDK同時提供兩個相同功能的類,一個是線程安全的,另外一個不是。線程安全的類通常性能上受影響
線程安全集合
Java中的集合接口
Collections API提供了一組包裝器方法來使集合線程安全,同時仍然可變。 Java API提供了進一步的裝飾器
線程安全包裝
public static <T> Collection<T> synchronizedCollection(Collection<T> c);
public static <T> Set<T> synchronizedSet(Set<T> s);
public static <T> List<T> synchronizedList(List<T> list);
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s);
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m);
包裝實現將他們全部的實際工做委託給指定的集合,但在集合提供的基礎上添加額外的功能。
這是裝飾者模式的一個例子(參見5-3節)
這些實現是匿名的;該庫不提供公共類,而是提供靜態工廠方法。
全部這些實現均可以在Collections類中找到,該類僅由靜態方法組成。
同步包裝將自動同步(線程安全)添加到任意集合。
不要繞開包裝
確保拋棄對底層非線程安全集合的引用,並僅經過同步包裝來訪問它。
新的HashMap只傳遞給synchronizedMap(),而且永遠不會存儲在其餘地方。
底層的集合仍然是可變的,引用它的代碼能夠規避不變性。
在使用synchronizedMap(hashMap)以後,不要再參數hashMap共享給其餘線程,不要保留別名,必定要完全銷燬
迭代器仍然不是線程安全的
儘管方法調用集合自己(get(),put(),add()等)如今是線程安全的,但從集合建立的迭代器仍然不是線程安全的。 即便在線程安全的集合類上,使用迭代器也是不安全的
此迭代問題的解決方案將是在須要迭代它時獲取集合的鎖。除非使用鎖機制
原子操做不足以阻止競爭
您使用同步收集的方式仍可能存在競爭條件。
考慮這個代碼,它檢查列表是否至少有一個元素,而後獲取該元素:
即便您將list放入同步列表中,此代碼仍可能存在競爭條件,由於另外一個線程可能會刪除isEmpty()調用和get()調用之間的元素。
同步映射確保containsKey(),get()和put()如今是原子的,因此從多個線程使用它們不會損害映射的rep不變量。
可是這三個操做如今能夠以任意方式相互交織,這可能會破壞緩存中須要的不變量:若是緩存將整數x映射到值f,那麼當且僅當f爲真時x是素數。
若是緩存永遠失敗這個不變量,那麼咱們可能會返回錯誤的結果。
注意
咱們必須爭論containsKey(),get()和put()之間的競爭不會威脅到這個不變量。
......在註釋中自證線程
須要對安全性進行這種仔細的論證 - 即便在使用線程安全數據類型時 - 也是併發性很難的主要緣由。
一個簡短的總結
經過共享可變數據的競爭條件實現安全的三種主要方式:
減小錯誤保證安全。
容易明白。
準備好改變。
最複雜也最有價值的threadsafe策略
回顧
數據類型或函數的線程安全性:在從多個線程使用時行爲正確,不管這些線程如何執行,無需額外協調。線程安全不該依賴於偶然
原理:併發程序的正確性不該該依賴於時間事件。
有四種策略可使代碼安全併發:
前三種策略的核心思想:
同步和鎖定
因爲共享可變數據的併發操做致使的競爭條件是災難性的錯誤 - 難以發現,重現和調試 - 咱們須要一種共享內存的併發模塊以實現彼此同步的方式。
不少時候,沒法知足上述三個條件...
使代碼安全併發的第四個策略是:
程序員來負責多線程之間對可變數據的共享操做,經過「同步」策略,避免多線程同時訪問數據
鎖是一種同步技術。
使用鎖能夠告訴編譯器和處理器你正在同時使用共享內存,因此寄存器和緩存將被刷新到共享存儲,確保鎖的全部者始終查看最新的數據。
阻塞通常意味着一個線程等待(再也不繼續工做)直到事件發生。
兩種鎖定操做
acquire容許線程獲取鎖的全部權。
release放棄鎖的全部權,容許另外一個線程得到它的全部權。
(1)同步塊和方法
鎖定
鎖是如此經常使用以致於Java將它們做爲內置語言功能提供。鎖是Java的語言提供的內嵌機制
可是,您不能在Java的內部鎖上調用acquire和release。 而是使用synchronized語句來獲取語句塊持續時間內的鎖定:
像這樣的同步區域提供互斥性:一次只能有一個線程處於由給定對象的鎖保護的同步區域中。
換句話說,你回到了順序編程世界,一次只運行一個線程,至少就其餘同步區域而言,它們指向同一個對象。
鎖定保護對數據的訪問
鎖用於保護共享數據變量。鎖保護共享數據
使用如下命令獲取與對象obj關聯的鎖定:
synchronized(obj){...}
鎖只與其餘獲取相同鎖的線程相互排斥。 全部對數據變量的訪問必須由相同的鎖保護。 注意:要互斥,必須使用同一個鎖進行保護
監視器模式
在編寫類的方法時,最方便的鎖是對象實例自己,即this。用ADT本身作鎖
做爲一種簡單的方法,咱們能夠經過在synchronized(this)內包裝全部對rep的訪問來守護整個類的表示。
監視器模式:監視器是一個類,它們的方法是互斥的,因此一次只能有一個線程在類的實例中。
每個觸及表示的方法都必須用鎖來保護,甚至像length()和toString()這樣的顯而易見的小代碼。
這是由於必須保護讀取以及寫入 - 若是讀取未被保留,則他們可能可以看處處於部分修改狀態的rep。
若是將關鍵字synchronized添加到方法簽名中,Java將像您在方法主體周圍編寫synchronized(this)同樣操做。
同步方法
同一對象上的同步方法的兩次調用不可能交錯。對同步的方法,多個線程執行它時不容許交錯,也就是說「按原子的串行方式執行」
同步語句/塊
同步方法和同步(this)塊之間有什麼區別?
兩者有何區別?
鎖定規則
鎖定規則是確保同步代碼是線程安全的策略。
咱們必須知足兩個條件:
這裏使用的監視器模式知足這兩個規則。表明中全部共享的可變數據 - 表明不變量依賴於 - 都被相同的鎖保護。
發生-前關係
這種發生-前關係,只是保證多個線程共享的對象經過一個特定語句寫入的內容對另外一個讀取同一對象的特定語句是可見的。
這是爲了確保內存一致性。
發生-前關係(a→ b)是兩個事件的結果之間的關係,所以若是在事件發生以前發生一個事件,那麼結果必須反映出,即便這些事件其實是無序執行的。
正式定義爲事件中最不嚴格的部分順序,以便:
像全部嚴格的偏序同樣,發生-前關係是傳遞的,非自反的和反對稱的。
原子數據訪問的關鍵字volatile
使用volatile(不穩定)變量可下降內存一致性錯誤的風險,由於任何對volatile變量的寫入都會在後續讀取該變量的同時創建happen-before關係。
這意味着對其餘線程老是可見的對volatile變量的更改。
更重要的是,這也意味着當一個線程讀取一個volatile變量時,它不只會看到volatile的最新變化,還會看到致使變化的代碼的反作用。
這是一個輕量級同步機制。
使用簡單的原子變量訪問比經過同步代碼訪問這些變量更有效,但須要程序員更多的關注以免內存一致性錯誤。
(3)處處使用同步?
那麼線程安全是否只需將synchronized關鍵字放在程序中的每一個方法上?
不幸的是,
首先,你實際上並不想同步方法。
另外一個以更慎重的方式使用同步的理由是,它最大限度地減小了訪問鎖的範圍。儘量減少鎖的範圍
與使用做爲表示內部對象的鎖並使用synchronized()塊適當並節省地獲取相比。
最後,處處使用同步並不夠實際。
假設咱們試圖經過簡單地將synchronized同步到它的聲明來解決findReplace的同步問題:
public static synchronized boolean findReplace(EditBuffer buf, ...)
synchronized關鍵字不是萬能的。
線程安全須要一個規範 - 使用監禁,不變性或鎖來保護共享數據。
這個紀律須要被寫下來,不然維護人員不會知道它是什麼。
Synchronized不是靈丹妙藥,你的程序須要嚴格遵照設計原則,先試試其餘辦法,實在作不到再考慮lock。
全部關於線程的設計決策也都要在ADT中記錄下來。
(4)活性:死鎖,飢餓和活鎖
活性
併發應用程序的及時執行能力被稱爲活躍性。
三個子度量標準:
(1)死鎖
若是使用得當,當心,鎖能夠防止競爭情況。
可是接下來的另外一個問題就是醜陋的頭腦。
因爲使用鎖須要線程等待(當另外一個線程持有鎖時獲取塊),所以可能會陷入兩個線程正在等待對方的狀況 - 所以都沒法取得進展。
死鎖描述了兩個或更多線程永遠被阻塞的狀況,等待對方。
死鎖:多個線程競爭鎖,相互等待對方釋放鎖
當併發模塊卡住等待對方執行某些操做時發生死鎖。
死鎖可能涉及兩個以上的模塊:死鎖的信號特徵是依賴關係的一個循環,例如, A正在等待B正在等待C正在等待A,它們都沒有取得進展。
死鎖的醜陋之處在於它
線程安全的鎖定方法很是強大,可是(與監禁和不可變性不一樣)它將阻塞引入程序。
線程必須等待其餘線程退出同步區域才能繼續。
在鎖定的狀況下,當線程同時獲取多個鎖時會發生死鎖,而且兩個線程最終被阻塞,同時持有鎖,每一個鎖都等待另外一個鎖釋放。
不幸的是,監視器模式使得這很容易作到。
死鎖:
因此A正在拿着哈利等着斯內普,而B正拿着斯內普等着哈利。
問題的實質是獲取多個鎖,並在等待另外一個鎖釋放時持有某些鎖。
死鎖解決方案1:鎖定順序
對須要同時獲取的鎖定進行排序,並確保全部代碼按照該順序獲取鎖定。
雖然鎖定順序頗有用(特別是在操做系統內核等代碼中),但它在實踐中有許多缺點。
首先,它不是模塊化的 - 代碼必須知道系統中的全部鎖,或者至少在其子系統中。
其次,代碼在獲取第一個鎖以前可能很難或不可能確切知道它須要哪些鎖。 它可能須要作一些計算來弄清楚。
死鎖解決方案2:粗略鎖定
要使用粗略鎖定 - 使用單個鎖來防止許多對象實例,甚至是程序的整個子系統。
可是,它有明顯的性能損失。
(2)飢餓
飢餓描述了線程沒法得到對共享資源的按期訪問而且沒法取得進展的狀況。
例如,假設一個對象提供了一個常常須要很長時間才能返回的同步方法。
由於其餘線程鎖時間太長,一個線程長時間沒法獲取其所需的資源訪問權(鎖),致使沒法往下進行。
(3)活鎖
線程一般會響應另外一個線程的動做而行動。
若是另外一個線程的動做也是對另外一個線程動做的響應,則可能致使活鎖。
與死鎖同樣,活鎖線程沒法取得進一步進展。
可是,線程並未被阻止 - 他們只是忙於響應對方恢復工做。
這與兩個試圖在走廊上相互傳遞的人至關:
(5)wait(),notify()和notifyAll()
保護塊
防禦區塊:這樣的區塊首先輪詢一個必須爲真的條件才能繼續。
假設,例如guardedJoy是一種方法,除非另外一個線程設置了共享變量joy,不然該方法不能繼續。
wait(),notify()和notifyAll()
如下是針對任意Java對象o定義的:
Object.wait()
Object.wait()會致使當前線程等待,直到另外一個線程調用此對象的notify()方法或notifyAll()方法。換句話說,這個方法的行爲就好像它只是執行調用wait(0)同樣。該操做使對象所處的阻塞/等待狀態,直到其餘線程調用該對象的notify()操做
Object.notify()/ notifyAll()
Object.notify()喚醒正在等待該對象監視器的單個線程。若是任何線程正在等待這個對象,則選擇其中一個線程來喚醒。隨機選擇一個在該對象上調用等方法的線程,解除其阻塞狀態
此方法只應由做爲此對象監視器全部者的線程調用。
線程以三種方式之一成爲對象監視器的全部者:
在守衛塊中使用wait()
wait()的調用不會返回,直到另外一個線程發出某個特殊事件可能發生的通知 - 儘管不必定是該線程正在等待的事件。
Object.wait()會致使當前線程等待,直到另外一個線程調用此對象的notify()方法或notifyAll()方法。
當wait()被調用時,線程釋放鎖並暫停執行。
在未來的某個時間,另外一個線程將得到相同的鎖並調用Object.notifyAll(),通知全部等待該鎖的線程發生重要事件:
第二個線程釋放鎖定一段時間後,第一個線程從新獲取鎖定,並經過從等待的調用返回來恢復。
wait(),notify()和notifyAll()
調用對象o的方法的線程一般必須預先鎖定o:
回想一下:開發ADT的步驟
指定:定義操做(方法簽名和規約)。
測試:開發操做的測試用例。測試套件包含基於對操做的參數空間進行分區的測試策略。
表明:選擇一個表明。
+++同步
作一個安全論證
併發性很難測試和調試!
因此若是你想讓本身和別人相信你的併發程序是正確的,最好的方法是明確地說明它沒有競爭,而且記下來。在代碼中註釋的形式增長說明:該ADT採起了什麼設計決策來保證線程安全
用於監禁的線程安全論證
由於您必須知道系統中存在哪些線程以及他們有權訪問哪些對象,所以在咱們僅就數據類型進行爭論時,監禁一般不是一種選擇。 除非你知道線程訪問的全部數據,不然Confinement沒法完全保證線程安全
所以,在這種狀況下,Confinement不是一個有用的論證。
併發程序設計的目標
併發程序是否能夠避免bug?
咱們關心三個屬性:
實踐中的併發
在真正的項目中一般採用什麼策略?
安全失敗帶來虛假的安全感。生存失敗迫使你面對錯誤。有利於活躍而不是安全的誘惑。
總結
生成一個安全無漏洞,易於理解和能夠隨時更改的併發程序須要仔細思考。
建立關於數據類型的線程安全參數,並在代碼中記錄它們。
獲取一個鎖容許一個線程獨佔訪問該鎖保護的數據,強制其餘線程阻塞 - 只要這些線程也試圖獲取同一個鎖。
監視器使用經過每種方法獲取的單個鎖來引用數據類型的表明。
獲取多個鎖形成的阻塞會形成死鎖的可能性。
什麼是併發編程?
進程,線程和時間片
交織和競爭條件
線程安全
如何作安全論證