并發在任何系統和編程語言中都有著重要的地位。
操作系統中的互斥和同步
在操作系統(假設單核)中,我們可以實現同時多個進程(軟件)的同時運行,其實取決于操作系統的中斷,也就是一個進程在cpu上執行一個時間片后就會被中斷,然后換上其它的進程上來執行,所以我們的感覺是進程都在并發的執行。或許你會問,為什么不一直執行,切換過去切換過來不消耗資源的嘛?對的,進程的切換時耗費資源,但是你得注意一種情況,就是很多時候你的進程其實并沒有消耗cpu,也就是它可能正在io阻塞中,這樣,cpu的切換的執行就會更加的高效,特別是那些io密集型的進程。
然而,我們在操作系統中,各個進程可能會訪問同一個資源(文件)(當然,直覺上這也是不可以的),所以必須想辦法去讓兩個或者多個進程不能同時對一個相同的資源去使用,最典型的就是不能同時去寫一個文本文件。當然這里我們必須想辦法讓他們不能同時使用(互斥),所以操作系統的典型處理就是在內核上個屏蔽中斷
和TSL(Test and Set Lock)
,軟件層次的信號量和PV操作
。通過這些方式,我們基本上就能夠完成操作系統的互斥和同步了。
java中的互斥和同步
但是在面向編程語言的,還有一種對互斥和同步的實現,那就是管程。
管程 (英語:Monitors,也稱為監視器) 是一種程序結構,結構內的多個子程序(對象或模塊)形成的多個工作線程互斥訪問共享資源。這些共享資源一般是硬件設備或一群變量。管程實現了在一個時間點,最多只有一個線程在執行管程的某個子程序。與那些通過修改數據結構實現互斥訪問的并發程序設計相比,管程實現很大程度上簡化了程序設計。
對,java就是實現了管程的。
互斥 - synchronized 和 lock
java中通過了關鍵字synchronized和java.util.concurrent.locks來提供互斥性。
- synchronized
- synchronized關鍵字修飾的代碼塊稱為同步代碼塊。
- 對于每個對象來說,synchronized方法共享一個鎖,也就是同時只有一個線程能夠訪問被synchronized標示的所有方法。
- 類鎖和對象鎖,類鎖指被
static
修飾的方法,對象就是new
出來的那個,其中類鎖是在class
上的鎖,所以所有調用的都共享同一個鎖,而對象鎖則不同的對象有不同的鎖。
- **java.util.concurrent.locks **
- 顯式的加鎖,常見的
ReentrantLock
。 - 使用顯式的鎖,你能夠更好的控制鎖的細節,及何時獲取釋放等。
- 在獲取
ReentrantLock
的同時可以interrupt()
,及在獲取鎖的同時可以被中斷。(當然在獲取synchronized的時候不能夠使用interrupt()
中斷)。
同步 - wait 和 notify
在說同步前,先講一講中斷interrupt
。
在java中,interrupt()
可以中斷線程,然后拋出InterruptException
。當然這中間也有他們的游戲規則,也就是當一個線程調用了interrupt()
后,如果線程處于wait()
或者sleep()
中就會立即拋出異常,否則就會等他們進入阻塞的調用后再拋出。其實這個也好理解,你不能強制立即讓它停止吧,當它還在活動的時候至少也得等它把手上的事做完吧。
java中通過了關鍵字wait和notify來提供同步性。
- wait
- wait 是釋放已經獲取的鎖,所以調用它的時候必須已經獲取鎖。及在synchronized鎖修飾的方法中的調用。
- 和
sleep
和yield
的對比,最重要的是wait
釋放了鎖,而sleep
則只是把線程掛起。
- notify,notifyAll
- 調用前自己得獲取了鎖
- 調用
notify
會喚醒一個wait
過的線程,notifyAll
則會喚醒所有被wait
的線程。 -
notifyAll
被調用的時候,只會喚醒那些等待相應的鎖的任務才會被喚醒,而不是ALL。