1. java 哪個鎖是非重入的
讀寫鎖 ReadWriteLock讀寫鎖維護了一對相關的鎖,一個用於只讀操作,一個用於寫入操作。只要沒有writer,讀取鎖可以由多個reader線程同時保持。寫入鎖是獨占的。
互斥鎖一次只允許一個線程訪問共享數據,哪怕進行的是只讀操作;讀寫鎖允許對共享數據進行更高級別的並發訪問:對於寫操作,一次只有一個線程(write線程)可以修改共享數據,對於讀操作,允許任意數量的線程同時進行讀取。
與互斥鎖相比,使用讀寫鎖能否提升性能則取決於讀寫操作期間讀取數據相對於修改數據的頻率,以及數據的爭用——即在同一時間試圖對該數據執行讀取或寫入操作的線程數。
讀寫鎖適用於讀多寫少的情況。
可重入讀寫鎖 ReentrantReadWriteLock
屬性ReentrantReadWriteLock 也是基於 AbstractQueuedSynchronizer 實現的,它具有下面這些屬性(來自Java doc文檔):
* 獲取順序:此類不會將讀取者優先或寫入者優先強加給鎖訪問的排序。
* 非公平模式(默認):連續競爭的非公平鎖可能無限期地推遲一個或多個reader或writer線程,但吞吐量通常要高於公平鎖。
* 公平模式:線程利用一個近似到達順序的策略來爭奪進入。當釋放當前保持的鎖時,可以為等待時間最長的單個writer線程分配寫入鎖,如果有一組等待時間大於所有正在等待的writer線程的reader,將為該組分配讀者鎖。
* 試圖獲得公平寫入鎖的非重入的線程將會阻塞,除非讀取鎖和寫入鎖都自由(這意味著沒有等待線程)。
* 重入:此鎖允許reader和writer按照 ReentrantLock 的樣式重新獲取讀取鎖或寫入鎖。在寫入線程保持的所有寫入鎖都已經釋放後,才允許重入reader使用讀取鎖。
writer可以獲取讀取鎖,但reader不能獲取寫入鎖。
* 鎖降級:重入還允許從寫入鎖降級為讀取鎖,實現方式是:先獲取寫入鎖,然後獲取讀取鎖,最後釋放寫入鎖。但是,從讀取鎖升級到寫入鎖是不可能的。
* 鎖獲取的中斷:讀取鎖和寫入鎖都支持鎖獲取期間的中斷。
* Condition 支持:寫入鎖提供了一個 Condition 實現,對於寫入鎖來說,該實現的行為與 ReentrantLock.newCondition() 提供的Condition 實現對 ReentrantLock 所做的行為相同。當然,此 Condition 只能用於寫入鎖。
讀取鎖不支持 Condition,readLock().newCondition() 會拋出 UnsupportedOperationException。
* 監測:此類支持一些確定是讀取鎖還是寫入鎖的方法。這些方法設計用於監視系統狀態,而不是同步控制。
實現AQS 回顧在之前的文章已經提到,AQS以單個 int 類型的原子變數來表示其狀態,定義了4個抽象方法( tryAcquire(int)、tryRelease(int)、tryAcquireShared(int)、tryReleaseShared(int),前兩個方法用於獨占/排他模式,後兩個用於共享模式 )留給子類實現,用於自定義同步器的行為以實現特定的功能。
對於 ReentrantLock,它是可重入的獨占鎖,內部的 Sync 類實現了 tryAcquire(int)、tryRelease(int) 方法,並用狀態的值來表示重入次數,加鎖或重入鎖時狀態加 1,釋放鎖時狀態減 1,狀態值等於 0 表示鎖空閑。
對於 CountDownLatch,它是一個關卡,在條件滿足前阻塞所有等待線程,條件滿足後允許所有線程通過。內部類 Sync 把狀態初始化為大於 0 的某個值,當狀態大於 0 時所有wait線程阻塞,每調用一次 countDown 方法就把狀態值減 1,減為 0 時允許所有線程通過。利用了AQS的共享模式。
現在,要用AQS來實現 ReentrantReadWriteLock。
一點思考問題
* AQS只有一個狀態,那麼如何表示 多個讀鎖 與 單個寫鎖 呢?
* ReentrantLock 里,狀態值表示重入計數,現在如何在AQS里表示每個讀鎖、寫鎖的重入次數呢?
* 如何實現讀鎖、寫鎖的公平性呢?
2. java如何實現線程安全,synchronized和lock的區別,可重入鎖
一、synchronized和lock的用法區別
synchronized:在需要同步的對象中加入此控制,synchronized在方法上,也在特定代碼塊中,括弧中表示需要鎖的對象。
lock:需要顯示指定起始位置和終止位置。一般使用ReentrantLock類做為鎖,多個線程中必須要使用一個ReentrantLock類做為對象才能保證鎖的生效。且在加鎖和解鎖處需要通過lock()和unlock()顯示指出。所以一般會在finally塊中寫unlock()以防死鎖。
二、synchronized和lock用途區別
synchronized原語和ReentrantLock在一般情況下沒有什麼區別,但是在非常復雜的同步應用中,請考慮使用ReentrantLock,特別是遇到下面2種需求的時候。
某個線程在等待一個鎖的控制權的這段時間需要中斷
2.需要分開處理一些wait-notify,ReentrantLock裡面的Condition應用,能夠控制notify哪個線程
3.具有公平鎖功能,每個到來的線程都將排隊等候
3. JAVA鎖有哪些種類,以及區別
常見的Java鎖有下面這些:
公平鎖/非公平鎖
可重入鎖
獨享鎖/共享鎖
互斥鎖/讀寫鎖
樂觀鎖/悲觀鎖
分段鎖
偏向鎖/輕量級鎖/重量級鎖
自旋鎖
這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。
公平鎖/非公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖。
非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先順序反轉或者飢餓現象。
對於JavaReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。
對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。
可重入鎖
可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖。說的有點抽象,下面會有一個代碼的示例。
對於JavaReentrantLock而言, 他的名字就可以看出是一個可重入鎖,其名字是Re entrant Lock重新進入鎖。
對於Synchronized而言,也是一個可重入鎖。可重入鎖的一個好處是可一定程度避免死鎖。
上面的代碼就是一個可重入鎖的一個特點,如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。
獨享鎖/共享鎖
獨享鎖是指該鎖一次只能被一個線程所持有。
共享鎖是指該鎖可被多個線程所持有。
對於JavaReentrantLock而言,其是獨享鎖。但是對於Lock的另一個實現類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。
讀鎖的共享鎖可保證並發讀是非常高效的,讀寫,寫讀 ,寫寫的過程是互斥的。
獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。
對於Synchronized而言,當然是獨享鎖。
互斥鎖/讀寫鎖
上面講的獨享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實現。
互斥鎖在Java中的具體實現就是ReentrantLock
讀寫鎖在Java中的具體實現就是ReadWriteLock
樂觀鎖/悲觀鎖
樂觀鎖與悲觀鎖不是指具體的什麼類型的鎖,而是指看待並發同步的角度。
悲觀鎖認為對於同一個數據的並發操作,一定是會發生修改的,哪怕沒有修改,也會認為修改。因此對於同一個數據的並發操作,悲觀鎖採取加鎖的形式。悲觀的認為,不加鎖的並發操作一定會出問題。
樂觀鎖則認為對於同一個數據的並發操作,是不會發生修改的。在更新數據的時候,會採用嘗試更新,不斷重新的方式更新數據。樂觀的認為,不加鎖的並發操作是沒有事情的。
從上面的描述我們可以看出,悲觀鎖適合寫操作非常多的場景,樂觀鎖適合讀操作非常多的場景,不加鎖會帶來大量的性能提升。
悲觀鎖在Java中的使用,就是利用各種鎖。
樂觀鎖在Java中的使用,是無鎖編程,常常採用的是CAS演算法,典型的例子就是原子類,通過CAS自旋實現原子操作的更新。
分段鎖
分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其並發的實現就是通過分段鎖的形式來實現高效的並發操作。
我們以ConcurrentHashMap來說一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱為Segment,它即類似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當需要put元素的時候,並不是對整個hashmap進行加鎖,而是先通過hashcode來知道他要放在那一個分段中,然後對這個分段進行加鎖,所以當多線程put的時候,只要不是放在一個分段中,就實現了真正的並行的插入。
但是,在統計size的時候,可就是獲取hashmap全局信息的時候,就需要獲取所有的分段鎖才能統計。
分段鎖的設計目的是細化鎖的粒度,當操作不需要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操作。
偏向鎖/輕量級鎖/重量級鎖
這三種鎖是指鎖的狀態,並且是針對Synchronized。在Java 5通過引入鎖升級的機制來實現高效Synchronized。這三種鎖的狀態是通過對象監視器在對象頭中的欄位來表明的。
偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。降低獲取鎖的代價。
輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。
重量級鎖是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓其他申請的線程進入阻塞,性能降低。
自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環會消耗CPU。
4. java 鎖有幾種
樂觀鎖/悲觀鎖
樂觀鎖與悲觀鎖不是指具體的什麼類型的鎖,而是指看待並發同步的角度。
悲觀鎖認為對於同一個數據的並發操作,一定是會發生修改的,哪怕沒有修改,也會認為修改。因此對於同一個數據的並發操作,悲觀鎖採取加鎖的形式。悲觀的認為,不加鎖的並發操作一定會出問題。
樂觀鎖則認為對於同一個數據的並發操作,是不會發生修改的。在更新數據的時候,會採用嘗試更新,不斷重新的方式更新數據。樂觀的認為,不加鎖的並發操作是沒有事情的。
從上面的描述我們可以看出,悲觀鎖適合寫操作非常多的場景,樂觀鎖適合讀操作非常多的場景,不加鎖會帶來大量的性能提升。
公平鎖/非公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖。
非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。
優點:在於吞吐量比公平鎖大。
缺點:可能會造成優先順序反轉或者某些線程飢餓現象(一直拿不到鎖)。
對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。
對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。
可重入鎖
可重入鎖的概念是自己可以再次獲取自己的內部鎖。
舉個例子,比如一條線程獲得了某個對象的鎖,此時這個對象鎖還沒有釋放,當其再次想要獲取這個對象的鎖的時候還是可以獲取的(如果不可重入的鎖的話,此刻會造成死鎖)。說的更高深一點可重入鎖是一種遞歸無阻塞的同步機制。
對於Java ReentrantLock而言, 他的名字就可以看出是一個可重入鎖,其名字是Re entrant Lock重新進入鎖。
對於Synchronized而言,也是一個可重入鎖。可重入鎖的一個好處是可一定程度避免死鎖。
獨享鎖/共享鎖
獨享鎖是指該鎖一次只能被一個線程所持有。
共享鎖是指該鎖可被多個線程所持有。
對於Java ReentrantLock(互斥鎖)而言,其是獨享鎖。
但是對於Lock的另一個實現類ReadWriteLock(讀寫鎖),其讀鎖是共享鎖,其寫鎖是獨享鎖。讀鎖的共享鎖可保證並發讀是非常高效的,讀寫,寫讀 ,寫寫的過程是互斥的。
對於Synchronized而言,當然是獨享鎖。
分段鎖
分段鎖其實是一種鎖的設計,並不是具體的一種鎖。對於ConcurrentHashMap而言,其並發的實現就是通過分段鎖的形式來實現高效的並發操作。
我們以ConcurrentHashMap來說一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱為Segment,它即類似於HashMap(JDK7與JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當需要put元素的時候,並不是對整個hashmap進行加鎖,而是先通過hashcode來知道他要放在那一個分段中,然後對這個分段進行加鎖,所以當多線程put的時候,只要不是放在一個分段中,就實現了真正的並行的插入。
但是,在統計size的時候,可就是獲取hashmap全局信息的時候,就需要獲取所有的分段鎖才能統計。
分段鎖的設計目的是細化鎖的粒度,當操作不需要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操作。
互斥鎖:
無法獲取瑣時,進線程立刻放棄剩餘的時間片並進入阻塞(或者說掛起)狀態,同時保存寄存器和程序計數器的內容(保存現場,上下文切換的前半部分),當可以獲取鎖時,進線程激活,等待被調度進CPU並恢復現場(上下文切換下半部分)
上下文切換會帶來數十微秒的開銷,不要在性能敏感的地方用互斥鎖
讀寫鎖:
1)多個讀者可以同時進行讀
2)寫者必須互斥(只允許一個寫者寫,也不能讀者寫者同時進行)
3)寫者優先於讀者(一旦有寫者,則後續讀者必須等待,喚醒時優先考慮寫者)
自旋鎖:
自旋鎖是指嘗試獲取鎖的線程不會立即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點是循環會消耗CPU。
5. java reentrantlock
java reentrantlock是什麼?一起來看下吧:
是一個可重入且獨占式的鎖,它具有與使用synchronized監視器鎖相同的基本行為和語義,但與synchronized關鍵字相比,它更靈活、更強大,增加了輪詢、超時、中斷等高級功能。ReentrantLock,顧名思義,它是支持可重入鎖的鎖,是一種遞歸無阻塞的同步機制。除此之外,該鎖還支持獲取鎖時的公平和非公平選擇。
ReentrantLock的內部類Sync繼承了AQS,分為公平鎖FairSync和非公平鎖NonfairSync。如果在絕對時間上,先對鎖進行獲取的請求你一定先被滿足,那麼這個鎖是公平的,反之,是不公平的。公平鎖的獲取,也就是等待時間最長的線程最優先獲取鎖,也可以說鎖獲取是順序的。ReentrantLock的公平與否,可以通過它的構造函數來決定。
事實上,公平鎖往往沒有非公平鎖的效率高,但是,並不是任何場景都是以TPS作為唯一指標,公平鎖能夠減少「飢餓」發生的概率,等待越久的請求越能夠得到優先滿足。
ReentrantLock是通過自定義同步器來實現鎖的獲取與釋放,我們以非公平鎖(默認)實現為例,對鎖的獲取和釋放進行詳解。
獲取鎖:
public ReentrantLock() { sync = new NonfairSync(); }
即內部同步組件為非公平鎖,獲取鎖的代碼為:
public void lock() { sync.lock(); }
釋放鎖:
成功獲取鎖的線程在完成業務邏輯之後,需要調用unlock()來釋放鎖:
public void unlock() { sync.release(1); }
unlock()調用NonfairSync類的release(int)方法釋放鎖,release(int)方法是定義在AQS中的方法:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
tryRelease(int)是子類需要實現的方法:
protected final boolean tryRelease(int releases) { // 計算新的狀態值 int c = getState() - releases; // 判斷當前線程是否是持有鎖的線程,如果不是的話,拋出異常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 新的狀態值是否為0,若為0,則表示該鎖已經完全釋放了,其他線程可以獲取同步狀態了 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 更新狀態值 setState(c); return free; }
如果該鎖被獲取n次,那麼前(n-1)次tryRelease(int)方法必須返回false,只有同步狀態完全釋放了,才能返回true。可以看到,該方法將同步狀態是否為0作為最終釋放的條件,當狀態為0時,將佔有線程設為null,並返回true,表示釋放成功。