已經習慣了阿里面試官的冷笑:用過Semaphore吧,不妨說說?html
本質就是 信號量模型,模型圖以下:linux
其中的 計數器 和 等待隊列 對外部是透明的,僅能經過提供的三大方法訪問它們。
詳細說說哪三大方法?面試
init()
用於設置計數器的初始值。數據庫
down()
計數器-1。若此時計數器<0,則當前線程被 阻塞。安全
up()
計數器+1。若此時計數器≤0,則喚醒 等待隊列 中的一個線程,並將其從【等待隊列】移除。有同窗可能會認爲這裏的判斷條件應該≥0,估計你是理解成生產者-消費者模式中的生產者了。能夠反過來想,>0 意味着沒有阻塞的線程,因此只有 ≤0 時才須要喚醒一個等待的線程。併發
down()、up()應配對使用,並按序使用:ui
先調用down(),獲取鎖
執行處理完後,調用up(),釋放鎖
若信號量init值爲1,併發場景下應該不會出現>0狀況,除非故意調先用up(),但這也失去了信號量的意義。url
注意,這些方法都是原子性的,由信號量模型的實現方保證。JDK裏的信號量模型就是由Semaphore實現,Semaphore保證了這三個方法都是原子操做。.net
talk is cheap,show me code?
信號量模型中的down()、up()最先被稱爲P操做和V操做,信號量模型也稱PV原語。還有的人會用semWait()和semSignal()表達它們,叫法不一樣,語義都相同。JUC的acquire()、release()分別對應down()和up()。線程
就像信號燈,必須先檢查是否爲綠燈才能經過。好比累加器,count+=1操做是個臨界區,只容許一個線程執行,也就是說要保證互斥。
假設線程t一、t2同時訪問add(),當同時調用acquire時,因爲acquire是個原子操做,僅會有一個線程(假設t1)把信號量裏的計數器減爲0,t2則是將計數器減爲-1:
對t1,信號量裏面的計數器的值是0,≥0,因此t1不會被阻塞,而是繼續執行
對t2,信號量裏面的計數器的值是-1,<0,因此t2被阻塞
因此此時只有t1會進入臨界區執行count+=1。
當t1執行release(),信號量裏計數器的值是-1,加1以後的值是0,≤0,根據up(),此時等待隊列中的t2會被喚醒。因而t2在t1執行完臨界區代碼後,纔得到進入臨界區執行的機會,這就保證了互斥。
既然有JDK提供了Lock,爲啥還要提供一個Semaphore ?
實現互斥鎖,僅是 Semaphore的部分功能,Semaphore還能夠容許多個線程訪問一個臨界區。
最多見的就是各類池化資源,好比數據庫鏈接池,同一時刻,容許多個線程同時使用鏈接池。每一個鏈接在被釋放前,不容許其餘線程使用。
對象池要求一次性建立出N個對象,以後全部的線程重複利用這N個對象,固然對象在被釋放前,也是不容許其餘線程使用的。因此核心就是限流器,這裏的限流指不容許多於N個線程同時進入臨界區。
如何快速實現一個這樣的限流器呢?
那就是信號量。把計數器的值設置成對象池裏對象的個數N便可:
注意這裏使用的是 Vector,進入臨界區的N個線程不安全。add/remove都是不安全的。好比 ArrayList remove() :
好的,請回家等通知吧!