參考文章: 1. java 多線程:synchronized 關鍵字用法(修飾類,方法,靜態方法,代碼塊) 2. Java 多線程:Lock 接口(接口方法分析,ReentrantLock,ReadWriteLock) 3. synchronized 與 Lock 的那點事 4. Java并發編程:Lock 5. ReentrantLock(重入鎖)以及公平性 參考書籍:《瘋狂Java講義(第二版)》。
創建一個賬戶類Account
,有姓名和賬戶余額屬性以及get
、set
方法:
線程類是實現Runnable
接口的Bank
類,代碼如下:
main
函數如下:
運行結果如下:
取錢成功!取出:1200.0 元!取錢成功!取出:1200.0 元!Rojer賬戶余額為:800.0 元!Rojer賬戶余額為:800.0 元! 從結果中看出,兩個線程都取錢成功了,這樣銀行是會虧死的,現在要實現一個時間段內只能有一個線程執行取錢操作,可以用synchronized
關鍵字來進行加鎖。
注意,synchronized
不能修飾接口中的方法,而且父類中的synchronized
方法被子類繼承以后,是沒有繼承synchronized
的,如果要使用,還需要加上synchronized
關鍵字。 用synchronized
關鍵字來修飾Bank
類的getMoney
方法:
運行結果如下:
取錢成功!取出:1200.0 元!Rojer賬戶余額為:800.0 元!取錢失敗,余額不足! 可以看出程序運行結果和我們期待的一樣,只有一個線程取錢成功,第二個線程取錢的時候失敗了,因為余額不足。 用synchronized
修飾方法時,所有的synchronized
修飾的方法都被鎖住了,只能供當前擁有鎖的線程使用,但是其他的非synchronized
修飾的方法沒有被鎖住,是可以正常調用的。HashTable
中就是這樣實現線程安全的,可以參考我的另一篇文章-深入JDK源碼之Hashtable。所以說,用synchronized來修飾方法是重量級的。
修飾代碼塊就是以下面的方式,用synchronized
關鍵字“包裹”住要同步的代碼塊:
括號中可以是一個對象,也可以是一個類的class。如果是一個對象的話,那么只對該對象的線程進行加鎖,如果是修飾了一個類的class,如上文的Bank.class
,那么無論有多少個Bank
對象,并發時只能有一個對象的一個線程訪問同步代碼塊,其他線程都要等待,但是訪問沒有synchronized
鎖住的代碼塊不受影響。
修改getMoney
代碼如下所示:
執行程序結果如下:
取錢成功!取出:1200.0 元!Rojer賬戶余額為:800.0 元!取錢失敗,余額不足! 修改getMoney
代碼,鎖住賬戶account
對象,如下所示:
運行結果如下:
取錢成功!取出:1200.0 元!Rojer賬戶余額為:800.0 元!取錢失敗,余額不足! synchronized
修飾的是私有的account
對象,因此當前對象的其他線程如果要使用account
對象,就必須先拿到對象的鎖才可以。 相比于synchronized
修飾方法,修飾代碼段的同步粒度更小,更加輕量。
靜態方法是所有類的對象共享的,因此用synchronized
修飾靜態方法,其實就是修飾類,即一個線程擁有該靜態方法的監視器的時候,其他所有該類的對象,或者直接調用該靜態方法的線程,都需要等待當前線程釋放監視器。
從 Java 5 開始,Java提供了一種更為強大的線程同步機制——通過顯示定義同步鎖對象來實現同步,在這種機制下,同步鎖由Lock對象充當。 Lock提供了比synchronized方法和synchronized代碼塊更廣泛的鎖定操作,Lock允許實現更靈活的結構,可以具有差別很大的屬性,并且支持多個相關的Condition對象。
—瘋狂Java講義 Lock
是java.util.concurrent.locks
包中的一個接口,源碼如下:
ReentrantLock
(可重入鎖)是Lock
接口的實現類,可重入,也就是可以在當前線程上對資源再加一個鎖,相應的有一個值在記錄加了幾重鎖,如果釋放鎖時沒有釋放完,則會報錯。同時,ReentrantLock
有個特性就是公平性,這個后面再說。
還有一個接口ReadWriteLock,顧名思義是讀寫鎖:
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing */ Lock writeLock();} ReentrantReadWriteLock
實現了ReadWriteLock
接口,如果調用readLock().lock()
的話,被當前線程鎖住的資源,其他所有線程都可以進行讀,但是不能進行寫,也不允許其他線程進行writeLock().lock()
來對當前資源進行加寫鎖;如果一個線程調用寫鎖writeLock().lock()
,那么之后當前線程可以進行寫操作,其他線程不能同時寫。
上面說到的公平性,如果是非公平的,就是JVM決定喚醒哪個等待線程獲得鎖,而公平的鎖,即喚醒當前在等待的線程中,等待時間最長的那個線程獲得鎖。默認情況下,ReentrantLock
和ReentrantReadWriteLock
都是非公平鎖,但是可以設置為公平鎖,相對于公平鎖,非公平鎖有更好的效率,因為公平的獲取鎖沒有考慮到操作系統對線程的調度因素,這樣造成JVM對于等待中的線程調度次序和操作系統對線程的調度之間的不匹配,而且對于鎖的快速且重復的獲取過程中,連續獲取的概率是非常高的,而公平鎖會壓制這種情況,雖然公平性得以保障,但是響應比卻下降了。
相比于synchronized
的笨重,Lock
更加靈活,如果獲取synchronized
鎖的線程阻塞了,那么其他等待鎖的線程只能等待,十分影響程序效率,而Lock
可以讓線程只等待一段時間或者中斷線程的等待,還可以加讀鎖,同時多個線程都可以進行讀,效率十分高。 但是Lock
實現鎖需要程序員多操點心了,獲取synchronized
鎖的線程執行完畢之后就會釋放鎖,不需要手動釋放,而且如果線程發生異常,JVM會讓線程自動釋放鎖,但是Lock
鎖需要程序員手動釋放鎖,并且要處理異常。
新聞熱點
疑難解答