JAVA显式锁,各种锁分类
顯示鎖優勢
使用Java內置鎖時,不需要通過Java代碼顯式地對同步對象的監視器進行搶占和釋放,這些工作由JVM底層完成,而且任何一個Java對象都能作為一個內置鎖使用,所以Java的對象鎖使用起來非常方便。但是,Java內置鎖的功能相對單一,不具備一些比較高級的鎖功能,比如:(1)限時搶鎖:在搶鎖時設置超時時長,如果超時還未獲得鎖就放棄,不至于無限等下去。(2)可中斷搶鎖:在搶鎖時,外部線程給搶鎖線程發一個中斷信號,就能喚起等待鎖的線程,并終止搶占過程。(3)多個等待隊列:為鎖維持多個等待隊列,以便提高鎖的效率。比如在生產者-消費者模式實現中,生產者和消費者共用一把鎖,該鎖上維持兩個等待隊列,即一個生產者隊列和一個消費者隊列。除了以上功能問題之外,Java對象鎖還存在性能問題。在競爭稍微激烈的情況下,Java對象鎖會膨脹為重量級鎖(基于操作系統的Mutex Lock實現),而重量級鎖的線程阻塞和喚醒操作需要進程在內核態和用戶態之間來回切換,導致其性能非常低。所以,迫切需要提供一種新的鎖來提升爭用激烈場景下鎖的性能。
從Lock提供的接口方法可以看出,顯式鎖至少比Java內置鎖多了以下優勢:
(1)可中斷獲取鎖使用synchronized關鍵字獲取鎖的時候,如果線程沒有獲取到被阻塞,阻塞期間該線程是不響應中斷信號(interrupt)的;而調用Lock.lockInterruptibly()方法獲取鎖時,如果線程被中斷,線程將拋出中斷異常。
(2)可非阻塞獲取鎖使用synchronized關鍵字獲取鎖時,如果沒有成功獲取,線程只有被阻塞;而調用Lock.tryLock()方法獲取鎖時,如果沒有獲取成功,線程也不會被阻塞,而是直接返回false。
(3)可限時搶鎖調用Lock.tryLock(long time,TimeUnit unit)方法,顯式鎖可以設置限定搶占鎖的超時時間。而在使用synchronized關鍵字獲取鎖時,如果不能搶到鎖,線程只能無限制阻塞。
可重入鎖ReentrantLock
ReentrantLock是一個可重入的獨占(或互斥)鎖,其中兩個修飾詞的含義為:
(1)可重入的含義:表示該鎖能夠支持一個線程對資源的重復加鎖,也就是說,一個線程可以多次進入同一個鎖所同步的臨界區代碼塊。比如,同一線程在外層函數獲得鎖后,在內層函數能再次獲取該鎖,甚至多次搶占到同一把鎖。
(2)獨占的含義:在同一時刻只能有一個線程獲取到鎖,而其他獲取鎖的線程只能等待,只有擁有鎖的線程釋放了鎖后,其他的線程才能夠獲取鎖。
用可重入鎖實現,CBA順尋執行。(其實程序有漏洞,但可以幫助理解)
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock;public class JUCStudy {private static final ReentrantLock reentrantLock1 = new ReentrantLock();private static final Condition condition1 = reentrantLock1.newCondition();private static final ReentrantLock reentrantLock2 = new ReentrantLock();private static final Condition condition2 = reentrantLock2.newCondition();public static void main(String[] args) throws InterruptedException {Thread myThreadA = new Thread(new MyThreadA());Thread myThreadB = new Thread(new MyThreadB());Thread myThreadC = new Thread(new MyThreadC());myThreadA.start();Thread.sleep(100);myThreadB.start();Thread.sleep(100);myThreadC.start();}static class MyThreadA implements Runnable {@Overridepublic void run() {reentrantLock1.lock();try {condition1.await();System.out.println('A');} catch (InterruptedException e) {e.printStackTrace();reentrantLock1.unlock();}}}static class MyThreadB implements Runnable {@Overridepublic void run() {reentrantLock1.lock();reentrantLock2.lock();try {condition2.await();System.out.println('B');condition1.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {reentrantLock2.unlock();reentrantLock1.unlock();}}}static class MyThreadC implements Runnable {@Overridepublic void run() {reentrantLock2.lock();try {System.out.println('C');condition2.signal();} catch (Exception e) {e.printStackTrace();} finally {reentrantLock2.unlock();}}} }一、悲觀鎖和樂觀鎖
獨占鎖其實就是一種悲觀鎖,Java的synchronized是悲觀鎖。悲觀鎖可以確保無論哪個線程持有鎖,都能獨占式訪問臨界區。雖然悲觀鎖的邏輯非常簡單,但是存在不少問題。
(1)在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題。(2)一個線程持有鎖后,會導致其他所有搶占此鎖的線程掛起。(3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖,就會導致線程的優先級倒置,從而引發性能風險。
解決以上悲觀鎖的這些問題的有效方式是使用樂觀鎖去替代悲觀鎖。與之類似,數據庫操作中的帶版本號數據更新、JUC包的原子類,都使用了樂觀鎖的方式提升性能。
樂觀鎖一種比較典型的就是CAS原子操作,JUC強大的高并發性能是建立在CAS原子之上的。CAS操作中包含三個操作數:需要操作的內存位置(V)、進行比較的預期原值(A)和擬寫入的新值(B)。如果內存位置V的值與預期原值A相匹配,那么處理器會自動將該位置的值更新為新值B;否則處理器不做任何操作。
樂觀鎖是一種思想,而CAS是這種思想的一種實現。
二、公平鎖和非公平鎖
synchronized內置鎖是一種非公平鎖,默認情況下ReentrantLock鎖也是非公平鎖。
非公平鎖是指多個線程獲取鎖的順序并不一定是其申請鎖的順序,有可能后申請的線程比先申請的線程優先獲取鎖,搶鎖成功的次序不一定體現為FIFO(先進先出)順序。非公平鎖的優點在于吞吐量比公平鎖大,它的缺點是有可能會導致線程優先級反轉或者線程饑餓現象。
三、可中斷鎖與不可中斷鎖
可中斷鎖是指搶占過程可以被中斷的鎖,JUC的顯式鎖(如ReentrantLock)是一個可中斷鎖。不可中斷鎖是指搶占過程不可以被中斷的鎖,如Java的synchronized內置鎖就是一個不可中斷鎖。
四、共享鎖與獨占鎖
在訪問共享資源之前進行加鎖操作,在訪問完成之后進行解鎖操作。按照“是否允許在同一時刻被多個線程持有”來區分,鎖可以分為共享鎖與獨占鎖。
Java中的Synchronized內置鎖和ReentrantLock顯式鎖都是獨占鎖。
共享鎖就是在同一時刻允許多個線程持有的鎖。當然,獲得共享鎖的線程只能讀取臨界區的數據,不能修改臨界區的數據。JUC中的共享鎖包括Semaphore(信號量)、ReadLock(讀寫鎖)中的讀鎖、CountDownLatch倒數閂。
package com.yucheng.jvm;import java.util.Date; import java.util.concurrent.Semaphore;public class SemaphoreStudy {public static void main(String[] args) throws InterruptedException {SemaphoreStudy semaphoreStudy = new SemaphoreStudy();semaphoreStudy.k();}public void k() throws InterruptedException {Semaphore semaphore = new Semaphore(3);Runnable runnable = new Runnable() {@Overridepublic void run() {try {semaphore.acquire(1);Thread.sleep(1000);System.out.println(new Date().toString() + Thread.currentThread());semaphore.release(1);} catch (InterruptedException e) {e.printStackTrace();}}};for (int i = 0; i < 1000; i++) {Thread t = new Thread(runnable);t.start();}} }五、讀寫鎖
讀寫鎖的內部包含兩把鎖:一把是讀(操作)鎖,是一種共享鎖;另一把是寫(操作)鎖,是一種獨占鎖。在沒有寫鎖的時候,讀鎖可以被多個線程同時持有。寫鎖是具有排他性的:如果寫鎖被一個線程持有,其他的線程不能再持有寫鎖,搶占寫鎖會阻塞;進一步來說,如果寫鎖被一個線程持有,其他的線程不能再持有讀鎖,搶占讀鎖也會阻塞。
總結
以上是生活随笔為你收集整理的JAVA显式锁,各种锁分类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA线程之间的通信
- 下一篇: JMeter打开jmx文件出错Canno