java中Locks的使用詳解
之前文章中我們講到,java中實(shí)現(xiàn)同步的方式是使用synchronized block。在java 5中,Locks被引入了,來提供更加靈活的同步控制。
本文將會深入的講解Lock的使用。
Lock和Synchronized Block的區(qū)別
我們在之前的Synchronized Block的文章中講到了使用Synchronized來實(shí)現(xiàn)java的同步。既然Synchronized Block那么好用,為什么會引入新的Lock呢?
主要有下面幾點(diǎn)區(qū)別:
- synchronized block只能寫在一個方法里面,而Lock的lock()和unlock()可以分別在不同的方法里面。
- synchronized block 不支持公平鎖,一旦鎖被釋放,任何線程都有機(jī)會獲取被釋放的鎖。而使用 Lock APIs則可以支持公平鎖。從而讓等待時間最長的線程有限執(zhí)行。
- 使用synchronized block,如果線程拿不到鎖,將會被Blocked。 Lock API 提供了一個tryLock() 的方法,可以判斷是否可以獲得lock,這樣可以減少線程被阻塞的時間。
- 當(dāng)線程在等待synchronized block鎖的時候,是不能被中斷的。如果使用Lock API,則可以使用 lockInterruptibly()來中斷線程。
Lock interface
我們來看下Lock interface的定義, Lock interface定義了下面幾個主要使用的方法:
- void lock() - 嘗試獲取鎖,如果獲取不到鎖,則會進(jìn)入阻塞狀態(tài)。
- void lockInterruptibly() - 和lock()很類似,但是它可以將正在阻塞的線程中斷,并拋出java.lang.InterruptedException。
- boolean tryLock() – 這是lock()的非阻塞版本,它回嘗試獲取鎖,并立刻返回是否獲取成功。
- boolean tryLock(long timeout, TimeUnit timeUnit) – 和tryLock()很像,只是多了一個嘗試獲取鎖的時間。
- void unlock() – unlock實(shí)例。
- Condition newCondition() - 生成一個和當(dāng)前Lock實(shí)例綁定的Condition。
在使用Lock的時候,一定要unlocked,以避免死鎖。所以,通常我們我們要在try catch中使用:
Lock lock = ...;
lock.lock();
try {
// access to the shared resource
} finally {
lock.unlock();
}
除了Lock接口,還有一個ReadWriteLock接口,在其中定義了兩個方法,實(shí)現(xiàn)了讀鎖和寫鎖分離:
- Lock readLock() – 返回讀鎖
- Lock writeLock() – 返回寫鎖
其中讀鎖可以同時被很多線程獲得,只要不進(jìn)行寫操作。寫鎖同時只能被一個線程獲取。
接下來,我們幾個Lock的常用是實(shí)現(xiàn)類。
ReentrantLock
ReentrantLock是Lock的一個實(shí)現(xiàn),什么是ReentrantLock(可重入鎖)呢?
簡單點(diǎn)說可重入鎖就是當(dāng)前線程已經(jīng)獲得了該鎖,如果該線程的其他方法在調(diào)用的時候也需要獲取該鎖,那么該鎖的lock數(shù)量+1,并且允許進(jìn)入該方法。
不可重入鎖:只判斷這個鎖有沒有被鎖上,只要被鎖上申請鎖的線程都會被要求等待。實(shí)現(xiàn)簡單
可重入鎖:不僅判斷鎖有沒有被鎖上,還會判斷鎖是誰鎖上的,當(dāng)就是自己鎖上的時候,那么他依舊可以再次訪問臨界資源,并把加鎖次數(shù)加一。
我們看下怎么使用ReentrantLock:
public void perform() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
下面是使用tryLock()的例子:
public void performTryLock() throws InterruptedException {
boolean isLockAcquired = lock.tryLock(1, TimeUnit.SECONDS);
if(isLockAcquired) {
try {
counter++;
} finally {
lock.unlock();
}
}
}
ReentrantReadWriteLock
ReentrantReadWriteLock是ReadWriteLock的一個實(shí)現(xiàn)。上面也講到了ReadWriteLock主要有兩個方法:
- Read Lock - 如果沒有線程獲得寫鎖,那么可以多個線程獲得讀鎖。
- Write Lock - 如果沒有其他的線程獲得讀鎖和寫鎖,那么只有一個線程能夠獲得寫鎖。
我們看下怎么使用writeLock:
Map<String,String> syncHashMap = new HashMap<>();
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock writeLock = lock.writeLock();
public void put(String key, String value) {
try {
writeLock.lock();
syncHashMap.put(key, value);
} finally {
writeLock.unlock();
}
}
public String remove(String key){
try {
writeLock.lock();
return syncHashMap.remove(key);
} finally {
writeLock.unlock();
}
}
再看下怎么使用readLock:
Lock readLock = lock.readLock();
public String get(String key){
try {
readLock.lock();
return syncHashMap.get(key);
} finally {
readLock.unlock();
}
}
public boolean containsKey(String key) {
try {
readLock.lock();
return syncHashMap.containsKey(key);
} finally {
readLock.unlock();
}
}
StampedLock
StampedLock也支持讀寫鎖,獲取鎖的是會返回一個stamp,通過該stamp來進(jìn)行釋放鎖操作。
上我們講到了如果寫鎖存在的話,讀鎖是無法被獲取的。但有時候我們讀操作并不想進(jìn)行加鎖操作,這個時候我們就需要使用樂觀讀鎖。
StampedLock中的stamped類似樂觀鎖中的版本的概念,當(dāng)我們在
StampedLock中調(diào)用lock方法的時候,就會返回一個stamp,代表鎖當(dāng)時的狀態(tài),在樂觀讀鎖的使用過程中,在讀取數(shù)據(jù)之后,我們回去判斷該stamp狀態(tài)是否變化,如果變化了就說明該stamp被另外的write線程修改了,這說明我們之前的讀是無效的,這個時候我們就需要將樂觀讀鎖升級為讀鎖,來重新獲取數(shù)據(jù)。
我們舉個例子,先看下write排它鎖的情況:
private double x, y;
private final StampedLock sl = new StampedLock();
void move(double deltaX, double deltaY) { // an exclusively locked method
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
再看下樂觀讀鎖的情況:
double distanceFromOrigin() { // A read-only method
long stamp = sl.tryOptimisticRead();
double currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
上面使用tryOptimisticRead()來嘗試獲取樂觀讀鎖,然后通過sl.validate(stamp)來判斷該stamp是否被改變,如果改變了,說明之前的read是無效的,那么需要重新來讀取。
最后,StampedLock還提供了一個將read鎖和樂觀讀鎖升級為write鎖的功能:
void moveIfAtOrigin(double newX, double newY) { // upgrade
// Could instead start with optimistic, not read mode
long stamp = sl.readLock();
try {
while (x == 0.0 && y == 0.0) {
long ws = sl.tryConvertToWriteLock(stamp);
if (ws != 0L) {
stamp = ws;
x = newX;
y = newY;
break;
}
else {
sl.unlockRead(stamp);
stamp = sl.writeLock();
}
}
} finally {
sl.unlock(stamp);
}
}
上面的例子是通過使用tryConvertToWriteLock(stamp)來實(shí)現(xiàn)升級的。
Conditions
上面講Lock接口的時候有提到其中的一個方法:
Condition newCondition();
Condition提供了await和signal方法,類似于Object中的wait和notify。
不同的是Condition提供了更加細(xì)粒度的等待集劃分。我們舉個例子:
public class ConditionUsage {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
上面的例子實(shí)現(xiàn)了一個ArrayBlockingQueue,我們可以看到在同一個Lock實(shí)例中,創(chuàng)建了兩個Condition,分別代表隊(duì)列未滿,隊(duì)列未空。通過這種細(xì)粒度的劃分,我們可以更好的控制業(yè)務(wù)邏輯。
本文的例子可以參考https://github.com/ddean2009/learn-java-concurrency/tree/master/Locks
到此這篇關(guān)于java中Locks的使用詳解的文章就介紹到這了,更多相關(guān)java Locks內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis教程之增刪改查_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了mybatis教程之增刪改查,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
Java實(shí)現(xiàn)在線編輯預(yù)覽office文檔詳解
PageOffice是一款在線的office編輯軟件,幫助Web應(yīng)用系統(tǒng)或Web網(wǎng)站實(shí)現(xiàn)用戶在線編輯Word、Excel、PowerPoint文檔,下面我們就來看看如何使用Java實(shí)現(xiàn)在線預(yù)覽office吧2024-01-01
JSP頁面pageEncoding和contentType屬性
有關(guān)于JSP頁面中pageEncoding和contentType屬性。2013-04-04
Spring Boot實(shí)現(xiàn)Undertow服務(wù)器同時支持HTTP2、HTTPS的方法
這篇文章考慮如何讓Spring Boot應(yīng)用程序同時支持HTTP和HTTPS兩種協(xié)議。小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
SpringMVC中的ResourceUrlProviderExposingInterceptor詳解
這篇文章主要介紹了SpringMVC中的ResourceUrlProviderExposingInterceptor詳解,ResourceUrlProviderExposingInterceptor是Spring MVC的一個HandlerInterceptor,用于向請求添加一個屬性,需要的朋友可以參考下2023-12-12

