一、 引入同步: 有一個很經典的案例,即銀行取款問題。我們可以先看下銀行取款的基本流程:
1)使用者輸入賬戶、密碼,系統判斷使用者的賬戶、密碼是否比對。
2)使用者輸入取款金額。
3)系統判斷賬戶金額是否大于取款金額。
4)如果餘額大于取款金額,則取款成功;如果餘額小于取款金額,則取款失敗。
假設,此時有兩個人,同時使用同一個賬戶并發取錢,我們模拟下取款流程:
接下來,提供一個取錢的線程類,該線程類根據執行賬戶、取錢數量進行取錢操作,取錢的邏輯是當其餘額不足時無法提取現金,當餘額足夠時系統吐出鈔票,餘額減少。
輸出:
---------- java ----------
乙取錢成功!吐出鈔票:800.0
甲取錢成功!吐出鈔票:800.0
餘額為: 200.0
餘額為: -600.0
輸出完成 (耗時 0 秒) - 正常終止
之是以會出現這樣的錯誤,是因為線程排程具有不确定性,在賬戶餘額隻有1000時,取出了1600,而且賬戶餘額出現了負值。
要解決該問題,java引入了同步螢幕,線上程開始執行同步代碼塊之前,必須先獲得同步螢幕的鎖定。
同步螢幕的目的: 阻止多個線程對同一個共享資源進行并發通路,是以通常推薦使用可能被并發通路的共享資源充當同步螢幕。
接下來,我們使用同步螢幕鎖定線程的執行體run()方法:
除了使用同步代碼塊之外,我們還可以使用同步方法。同步方法無須顯示指定同步螢幕,同步方法的同步螢幕是this,也就是對象本身。
通過通過方法可以非常友善的實作線程安全的類:
·該類的對象可以被多個線程安全的通路。
·每個線程調用該對象的任意方法之後都将得到正确的結果。
·每個線程調用該對象的任意方法之後,該對象的狀态依然保持合理狀态。
上面程式中增加了一個代表取錢的draw()方法,并使用了synchronized關鍵字修飾,該方法變為同步方法,同步方法的同步螢幕是this,是以對于同一個account賬戶而言,任意時刻隻能有一個線程account對象鎖定,然後進入draw()方法執行取錢操作。
接下來,我們看下并發的線程類該如何寫:
線程類無須事前取錢操作,而是直接調用account的draw()方法來執行取錢操作。由于已經使用了synchronized關鍵字修飾了draw()方法,同步方法的同步螢幕就是this,而this總代表調用該方法的對象——在上面的示例中,調用draw()方法的對象時account,是以多個線程并發修改一份account之前,必須先對account對象加鎖。
二、 同步鎖(lock)
lock提供了比synchronized方法和synchronized代碼塊更廣泛的鎖定操作,lock實作允許更靈活的結構。lock是控制多個線程對共享資源進行通路的工具。
某些鎖可能允許對共享資源的并發通路,比如readwritelock(讀寫鎖)。比較常用的lock有reentrantlock(可重入鎖),使用它可以顯式的加鎖、釋放鎖。
reentrantlock鎖具有可重入性,也就是說,一個線程可以對已被加鎖的reentrantlock鎖再次加鎖,reentrantlock對象會維持一個計數器來之宗lock()方法的嵌入調用,線程在每次調用lock()枷鎖後,必須顯示調用unlock()來釋放鎖,是以一段被鎖保護的代碼可以調用另一個被相同鎖保護的方法。
三、死鎖
當兩個線程互相等待對方釋放同步螢幕時就會發生死鎖,java虛拟機沒有監測,也沒有采取措施處理死鎖情況,是以多線程程式設計時應該采取避免死鎖出現。
死鎖的舉例: