標籤:-- 調用 而且 方法 必須 並發 退出 基本原理 而不是
同步:防止線程同時訪問共用資料。
鎖:是一種抽象,最多允許一個線程擁有它。 保持鎖定是一條線程告訴其他線程的:我正在改變這 個東西,現在不要觸摸它
兩個操作:擷取允許線程擷取鎖的所有權。 如果一個線程試圖擷取當前由另一個線程擁有的鎖,它將阻塞,直到另一個線程釋放該鎖。 此時,它將與任何其他嘗試擷取鎖的線程競爭。 一次只能有一個線程擁有該鎖。釋放鎖的所有權,允許另一個線程獲得它的所有權
使用鎖還會告知編譯器和處理器您正在同時使用共用記憶體,以便將寄存器和快取重新整理到共用儲存。 這可以確保鎖的所有者始終查看最新的資料
阻塞一般意味著線程等待(不再 繼續工作),直到一個事件發生。
同步塊和方法:鎖定:鎖是如此常用以至於Java將它們作為內建語言功能提供
同步是圍繞內部鎖或監視器鎖實體構建的, (API 規範通常將此實體簡稱為“監視器”)。在同步時:強制對象狀態的獨 占訪問和建立happens-before關係,內部鎖都起作用。
每個類及其所有對象執行個體都有一個鎖,Object has a lock :Object lock = new Object();
兩個基本的同步習慣用法:同步方法 同步語句/同步代碼塊
同步語句
同步語句必須指定提供內部鎖的對象
同步地區提供了互斥功能:一次只能有一個線程處於由給定對象的鎖保護的同步地區中。(順序執行)
例:
鎖用於保護共用資料變數。 如果所有對資料變數的訪問都被相同的鎖對象保護(被同步塊包圍),那麼這些訪問將被保證為原子 - 不被其他線程中斷
線上程t中使用synchronized (obj) { ... } 擷取與對象obj關聯的鎖只做一件事:阻止其他線程進入 synchronized(obj)塊,直到線程t完成其同步塊為止。
鎖只能確保與其他請求 擷取相同對象鎖的線程互斥訪問。所有對資料變數的訪問必須由相同的鎖保護。 你可以在一個鎖後面保護整個變數集合,但是所有模組必須同意他們將獲得並釋放哪個鎖
錯誤:擁有對象的鎖會自動阻止其他線程訪問該對象
鎖只能確保與其他請求擷取相同對象鎖的線程互斥訪問,如 果其他線程沒有使用synchronized (obj)或者利用了不同object的鎖 ,則同步會失效。
同步方法
當線程調用同步方法時,它會 自動擷取該方法對象的內部鎖,並在方法返回時釋放它。 即使返回是 由未捕獲的異常引起的,也會釋放鎖。
同一對象上的同步方法的兩次調用不會 有交錯現象。
當一個線程正在執行一個對象的同步方法時,所有其他線程 如果調用同一對象的同步方法塊,則會掛起執行,直到第一個線程針 對此對象的操作完成.
當一個同步方法退出時,它會自動建立一個與之後調用同 一個對象的同步方法的happens-before關係。這保證對象狀態的更改 對所有線程都是可見的
happens-before關係:保證了語句A記憶體的寫入對語句B是可見的, 也就是在B開始讀資料之前,A已經完成了資料的寫入。 確保記憶體一致性
原子操作
使用volatile變數可以降低記憶體一致性錯誤的風險,因為任何對volatile變數的寫入都會與隨後的同一個變數的讀取之間建立一個happen-before關係volatile變數的更改,對其他線程總是可見的(速度更快)
基本原理:每次使用此類變數時都到主存中進行讀取,而且,當成員變 量發生變化時,強迫線程將變化值回寫到共用記憶體。這樣在任何時刻, 兩個不同的線程總是看到某個成員變數的同一個值。避免虛擬機器採用寄 存器緩衝最佳化(線程可以把變數儲存在本地記憶體(比如機器的寄存器)中 ,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改 了一個變數的值,而另外一個線程還繼續使用它在寄存器中的變數值的 拷貝,造成資料的不一致。
volatile不能處理所有情況
volatile 不能提供必須的原子 特性,只能在有限的一些情形 下使用 volatile變數替代鎖: 對變數的寫操作不依賴於當前 值,變數的有效值獨立於任何 程式的狀態,包括變數的當前 狀態。
synchronized能否在所有地方應用?不能
同步對程式而言開銷很大,由於需要擷取鎖(並重新整理緩衝並與其他處理器通訊),因此進行同步方法調用可能需要更長的時間
當你不需要同步時,不要使用它。同步方法,意味著正在擷取一個鎖,而不考慮它是哪個鎖,或 者是否它是否是保護你將要執行的共用資料訪問的正確鎖。
將synchronized同步到某個方法,則一次只有一個線程可以調用,即使其他線程想要在不同的緩衝區上運行,這些緩衝區應該是安全的,它們仍然會被阻塞,直到單鎖被釋放,效能損失嚴重。
死結描述了兩個或更多線程 永遠被阻塞的情況,都在等待對方。
防止死結的一種方法是①對需要同時獲 取的鎖進行排序,並確保所有代碼按照該順序擷取鎖定
②粗粒度的鎖,用單個鎖來監控多個 對象執行個體(在最糟糕的情況下,程式可能基 本上是順序執行的,喪失了並發性。)
饑餓描述了線程無法獲得對共用資源的訪問,而無法取得進展的情況。
當共用資源由“貪婪”線程導致長時間不 可用時,會發生這種情況。
例如,假設一個對象提供了一個經常需要很長時間才能返回的同步方法。 如果一個線程頻繁地調用這個方法,那麼其他線程也需要經常同步訪問同一個對象
活鎖:如果一個線程的行為也是對 另一個線程的行為的響應,則可能導致活鎖。 與死結一樣,活鎖線程無法取得進一步進展,線程並未被阻止 ,他們只 是忙於響應對方恢複工作。
Thread.sleep(time) 讓當前線程暫停指定 時間的執行
Join()方法用於保持當前正在啟動並執行線程的執行,直到該線程死亡(執行完畢),才能繼續執行後續線程 (讓一 個線程等待另一個線程結束。(可能需要前麵線程的輸出結果作為輸入))
執行wait()後,當前線程會等待,直到其他線程調用 此對象的notify( ) 方法或 notifyAll( ) 方法。
在將來的某個時間,另一個線程將獲得相同的鎖並調用Object.notifyAll(),通知等待該鎖的所有線程發生了重要事件。第二個線程釋放鎖定一段時間後,第一個線程重新擷取鎖定並從等待的調用返回
低級可中斷阻塞方法 :Thread.sleep(), Thread.join(), or Object.wait()
阻塞方法:一般方法的完成只取 決於它所要做的事情,以及是否有足夠多可用的計算資源, 而阻塞方法的完成還取決於一 些外部的事件,例如計時器到期,I/O 完成,或者另一個線程的動作(釋 放一個鎖,設定一個標誌,或者將一個任務放在一個工作隊列中)。一般方法在 它們的工作做完後即可結束,而阻塞方法較難於預測,因為它們取決於外 部事件。阻塞方法可能影響響應能力,因為難於預測它們何時會結束。 阻塞方法可能因為等不到所 等的事件而無法終止,因此令阻塞方法可取消 就非常有用
interrupt()
當另一個線程通過調用 Thread.interrupt() 中斷一個線程時,會出現以下兩種情況之一。
①如果被中斷線程在執行一個低級可中斷阻塞方法,例如 Thread.sleep()、 Thread.join() 或Object.wait(),那麼它將取消阻塞並拋出 InterruptedException。
②interrupt() 只是設定線程的中斷狀態。
中斷狀態可以通過 Thread.isInterrupted() 來讀取,並且可以通過一個名為 Thread.interrupted() 的操作讀取和清除
中斷是禮貌地請求另一個線程在它願 意並且方便的時候停止它正在做的事情。
安全(錯誤的計算)
活躍度(沒有計算)
軟體構造 並發3(執行緒安全性)----鎖定和同步