鎖共有多種演算法,在併發場景中都是被常常用到,想必大家都已爐火純青般.....巴特!我們還有後浪同學們可能不熟悉,那我在這裡聊下鎖的用法和使用場景。 ...
一、開篇背景
“鎖”代表安全。在程式中(這裡指java)尤其多線程環境下,有了鎖的幫助,會給數據安全帶來保障,幫助線程更好的運作,避免競爭和互斥。
鎖共有15種演算法:樂觀鎖、悲觀鎖、自旋鎖、重入鎖、讀寫鎖、公平鎖、非公平鎖、共用鎖、獨占鎖、重量級鎖、輕量級鎖、偏向鎖、分段鎖、互斥鎖、同步鎖....一口氣輸出真的累,誰記這個啊。我們要吃現成的。ok,上面的一大堆在咱java里就是:
ReentrantLock,Synchronieed,ReentrantReadWriteLock,Atomic 全家桶,Concurrent全家桶
已上在併發場景中都是被常常用到,想必大家都已爐火純青般.....巴特!我們還有後浪同學們可能不熟悉,那我在這裡聊下鎖的用法和使用場景。
ReentrantLock:
ReentrantLock是一個互斥的可重入鎖。互斥的意思就是排他,獨占,只能一個線程獲取到鎖。可重入的意思就是單個線程可以多次重覆獲取鎖。它實現了悲觀鎖、重入鎖、獨占鎖、非公平鎖和互斥鎖的演算法。
常用方法
方法名 | 說明 | 異常 |
---|---|---|
lock() | 一直阻塞獲取鎖,直到獲取成功 | 無 |
lockInterruptibly() | 嘗試獲取鎖,直到獲取鎖或者線程被中斷 | InterruptedException |
tryLock() | 嘗試獲取空閑的鎖,獲取成功返回true,獲取失敗返回false,不會阻塞,立即返回 | 無 |
tryLock(long time, TimeUnit unit) | 嘗試在time時間內獲取空閑的鎖,在等待時間內可以被中斷 | InterruptedException |
unLock() | 釋放鎖 | 無 |
newCondition() | 返回當前鎖的一個condition實例,可以喚醒或等待線程 | 無 |
場景
遞歸嵌套的業務場景中,例如一棵樹型的業務邏輯,方法有嵌套和調用,這時候我從外層加鎖後,在遞歸遍歷多次,每次都要是同一把鎖,並且遞歸到其他層級時鎖還不能失效。這個時候就可以使用重入鎖了。江湖上還有個花名,叫遞歸鎖。
Synchronized:
Synchronized是悲觀鎖,預設是偏向鎖,解鎖失敗後會升級為輕量級鎖,當競爭繼續加劇,進入CAS自旋10次後會升級為重量級鎖。它實現了悲觀鎖、重入鎖(用關鍵字修飾方法或代碼段時)、獨占鎖,非公平鎖、輕量級鎖、重量級鎖、偏向鎖和同步鎖演算法。
使用方法
使用場景 | 說明 |
---|---|
修飾方法 | public synchronized void someMethod() |
修飾代碼塊 | public void someMethod() { synchronized (lockObject) { // 臨界區代碼 } } |
修飾靜態方法 | public static synchronized void someStaticMethod() |
修飾類 | public class MyClass { public void method1() { synchronized(MyClass.class) { // 同步代碼塊 } } public synchronized void method2() { // 同步方法 } } |
場景
多個線程訪問共用變數,為了保證期原子性就可以使用 synchronized 。一個典型的業務場景是銀行轉賬操作。假設有多個用戶在同時進行轉賬操作,需要確保轉賬過程的原子性和數據的一致性,避免出現重覆轉賬或者轉賬金額錯誤的情況。在這種情況下,可以使用 synchronized 關鍵字來實現同步訪問。
ReentrantReadWriteLock:
ReentrantReadWriteLock是Java提供的讀寫鎖,它支持多個線程同時讀取共用數據,但只允許一個線程進行寫操作。 他實現了讀寫鎖和共用鎖演算法。
常用方法
方法 | 說明 |
---|---|
readLock() | 獲取讀鎖 |
writeLock() | 獲取寫鎖 |
readLock().lock() | 獲取讀鎖並加鎖 |
writeLock().lock() | 獲取寫鎖並加鎖 |
readLock().unlock() | 釋放讀鎖 |
writeLock().unlock() | 釋放寫鎖 |
newCondition() | 創建與鎖相關聯的Condition實例,可以喚醒或等待線程 |
場景
讀寫鎖應用在讀多寫少的情況下。讀取時不涉及數據修改,寫入時需要互斥操作。現在基本所有的號稱效率高的準實時資料庫都有實現讀寫鎖的演算法。
Atomic 全家桶:
Atomic全家桶 我們已 AtomicInteger 為例。他的特點是實現了CAS演算法,同時解決了ABA問題保證原子性。還實現了自旋鎖的CLHLock演算法,用於CAS比較失敗後自旋等待。它實現了樂觀鎖、自旋鎖和輕量級鎖的演算法。
常用方法
方法 | 說明 |
---|---|
get(); | 獲取當前AtomicInteger對象的值 |
set(int newValue); | 將AtomicInteger對象的值設置為指定的新值 |
getAndSet(int newValue) | 將AtomicInteger對象的值設置為指定的新值,並返回設置前的舊值 |
incrementAndGet() | 將AtomicInteger對象的值遞增1,並返回遞增後的新值 |
decrementAndGet() | 將AtomicInteger對象的值遞減1,並返回遞減後的新值 |
getAndIncrement() | 先獲取AtomicInteger對象的當前值,然後將其遞增1,並返回獲取的舊值 |
getAndDecrement() | 先獲取AtomicInteger對象的當前值,然後將其遞減1,並返回獲取的舊值 |
addAndGet(int delta) | 將指定的值加到AtomicInteger對象的當前值上,並返回相加後的結果 |
getAndAdd(int delta) | 先獲取AtomicInteger對象的當前值,然後將指定的值加到當前值上,並返回獲取的舊值 |
場景
用來做計數器非常合適,再有就是線程通訊,數據共用。
Concurrent全家桶:
Concurrent全家桶我們已ConcurrentHashMap為代表。它實現了分段鎖演算法(Segmented Locking)的策略,將整個數據結構劃分成多個Segments,每個段都擁有獨立的鎖。
常用方法
方法 | 說明 |
---|---|
clear(); | 移除所有關係 |
containsKey(object value); | 檢查指定對象是都為表中的鍵 |
containsValue(object value); | 如果此映射將一個或多個健映射到指定值,返回true |
elements() | 返回此表值的枚舉 |
entrySet() | 返回此映射所包含的映射關係Set視圖 |
get() | 返回指定鍵映射到的值沒如果此映射不包含該鍵的映射關係,則返回null |
isEmpty() | 此映射不包含鍵值則返回true |
keys() | 返回此表中鍵的枚舉 |
put(K key, V value) | 指定將健映射到此表中的指定值 |
putAll(Map<? extends k, ? extends v> m) | 將指定映射中所有的映射關係複製到此映射中 |
size() | 返回此映射中的鍵值映射關係數 |
remove(object key) | 將鍵從此映射中移除 |
replace(K key, V value) | 只有目前將鍵的條目映射到給定值時,才替換該鍵的條目 |
場景
在java中ConcurrentHashMap,就是將數據分為16段,每一段都有單獨的鎖,並且處於不同鎖段的數據互不幹擾,以此來提升鎖的性能。
比如在秒殺扣庫存的場景中,現在的庫存中有2000個商品,用戶可以秒殺。為了防止出現超賣的情況,通常情況下,可以對庫存加鎖。如果有1W的用戶競爭同一把鎖,顯然系統吞吐量會非常低。為了提升系統性能,我們可以將庫存分段,比如:分為100段,這樣每段就有20個商品可以參與秒殺。在秒殺的過程中,先把用戶id獲取hash值,然後除以100取模。模為1的用戶訪問第1段庫存,模為2的用戶訪問第2段庫存,模為3的用戶訪問第3段庫存,後面以此類推,到最後模為100的用戶訪問第100段庫存。如此一來,在多線程環境中,可以大大的減少鎖的衝突。
二、重點分散式場景redisson和zk的鎖的介紹
Redisson
我們日常開發中用用的最多的場景還是分散式鎖。提到分散式鎖就不可迴避Redisson。WHY? 他就是權威好用。使用場景最多沒有之一。Redisson官方一共提供了8把鎖。我們逐一看一下。
1 可重入鎖(Reentrant Lock)
基於Redis的Redisson分散式可重入鎖**RLock**
Java對象實現了java.util.concurrent.locks.Lock
介面。同時還提供了非同步(Async)、反射式(Reactive)和RxJava2標準的介面。
public static void main(String[] args) throws InterruptedException {
// connects to 127.0.0.1:6379 by default
RedissonClient redisson = Redisson.create();
RLock lock = redisson.getLock("lock");
lock.lock(2, TimeUnit.SECONDS);
Thread t = new Thread() {
public void run() {
RLock lock1 = redisson.getLock("lock");
lock1.lock();
lock1.unlock();
};
};
t.start();
t.join();
redisson.shutdown();
}
大家都知道,如果負責儲存這個分散式鎖的Redisson節點宕機以後,而且這個鎖正好處於鎖住的狀態時,這個鎖會出現鎖死的狀態。為了避免這種情況的發生,Redisson內部提供了一個監控鎖的看門狗,它的作用是在Redisson實例被關閉前,不斷的延長鎖的有效期。預設情況下,看門狗的檢查鎖的超時時間是30秒鐘,也可以通過修改Config.lockWatchdogTimeout來另行指定。
另外Redisson還通過加鎖的方法提供了leaseTime
的參數來指定加鎖的時間。超過這個時間後鎖便自動解開了。
// 加鎖以後10秒鐘自動解鎖
// 無需調用unlock方法手動解鎖
lock.lock(10, TimeUnit.SECONDS);
// 嘗試加鎖,最多等待100秒,上鎖以後10秒自動解鎖
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}
Redisson同時還為分散式鎖提供了非同步執行的相關方法:
RLock lock = redisson.getLock("anyLock");
lock.lockAsync();
lock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = lock.tryLockAsync(100, 10, TimeUnit.SECONDS);
RLock
對象完全符合Java的Lock規範。也就是說只有擁有鎖的進程才能解鎖,其他進程解鎖則會拋出IllegalMonitorStateException
錯誤。但是如果遇到需要其他進程也能解鎖的情況,請使用分散式信號量Semaphore
對象.
2. 公平鎖(Fair Lock)
基於Redis的Redisson分散式可重入公平鎖也是實現了java.util.concurrent.locks.Lock
介面的一種RLock
對象。同時還提供了非同步(Async)、反射式(Reactive)和RxJava2標準的介面。它保證了當多個Redisson客戶端線程同時請求加鎖時,優先分配給先發出請求的線程。所有請求線程會在一個隊列中排隊,當某個線程出現宕機時,Redisson會等待5秒後繼續下一個線程,也就是說如果前面有5個線程都處於等待狀態,那麼後面的線程會等待至少25秒。
public static void main(String[] args) throws InterruptedException {
// connects to 127.0.0.1:6379 by default
RedissonClient redisson = Redisson.create();
RLock lock = redisson.getFairLock("test");
int size = 10;
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < size; i++) {
final int j = i;
Thread t = new Thread() {
public void run() {
lock.lock();
lock.unlock();
};
};
threads.add(t);
}
for (Thread thread : threads) {
thread.start();
thread.join(5);
}
for (Thread thread : threads) {
thread.join();
}
}
同樣也有看門狗機制來防止死鎖。另外Redisson還通過加鎖的方法提供了leaseTime
的參數來指定加鎖的時間。超過這個時間後鎖便自動解開了。
// 10秒鐘以後自動解鎖
// 無需調用unlock方法手動解鎖
fairLock.lock(10, TimeUnit.SECONDS);
// 嘗試加鎖,最多等待100秒,上鎖以後10秒自動解鎖
boolean res = fairLock.tryLock(100, 10, TimeUnit.SECONDS);
...
fairLock.unlock();
Redisson同時還為分散式可重入公平鎖提供了非同步執行的相關方法:
RLock fairLock = redisson.getFairLock("anyLock");
fairLock.lockAsync();
fairLock.lockAsync(10, TimeUnit.SECONDS);
Future<Boolean> res = fairLock.tryLockAsync(100, 10, TimeUnit.SECONDS);
3. 聯鎖(MultiLock)
基於Redis的Redisson分散式聯鎖**RedissonMultiLock**
對象可以將多個RLock
對象關聯為一個聯鎖,每個RLock
對象實例可以來自於不同的Redisson實例。
public static void main(String[] args) throws InterruptedException {
// connects to 127.0.0.1:6379 by default
RedissonClient client = Redisson.create();
// 同時加鎖:lock1 lock2 lock3 所有的鎖都上鎖成功才算成功。
RLock lock1 = client.getLock("lock1");
RLock lock2 = client.getLock("lock2");
RLock lock3 = client.getLock("lock3");
Thread t = new Thread() {
public void run() {
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
lock.lock();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
lock.unlock();
};
};
t.start();
t.join(1000);
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
lock.lock();
lock.unlock();
}
同樣也有看門狗機制來防止死鎖。另外Redisson還通過加鎖的方法提供了leaseTime
的參數來指定加鎖的時間。超過這個時間後鎖便自動解開了。
RedissonMultiLock lock = new RedissonMultiLock(lock1, lock2, lock3);
// 給lock1,lock2,lock3加鎖,如果沒有手動解開的話,10秒鐘後將會自動解開
lock.lock(10, TimeUnit.SECONDS);
// 為加鎖等待100秒時間,併在加鎖成功10秒鐘後自動解開
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();
4. 紅鎖(RedLock)
基於Redis的Redisson紅鎖RedissonRedLock
對象實現了Redlock介紹的加鎖演算法。該對象也可以用來將多個RLock
對象關聯為一個紅鎖,每個RLock
對象實例可以來自於不同的Redisson實例。
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同時加鎖:lock1 lock2 lock3
// 紅鎖在大部分節點上加鎖成功就算成功。
lock.lock();
...
lock.unlock();
。
public static void main(String[] args) throws InterruptedException {
// connects to 127.0.0.1:6379 by default
RedissonClient client1 = Redisson.create();
RedissonClient client2 = Redisson.create();
RLock lock1 = client1.getLock("lock1");
RLock lock2 = client1.getLock("lock2");
RLock lock3 = client2.getLock("lock3");
Thread t1 = new Thread() {
public void run() {
lock3.lock();
};
};
t1.start();
t1.join();
Thread t = new Thread() {
public void run() {
RedissonMultiLock lock = new RedissonRedLock(lock1, lock2, lock3);
lock.lock();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
}
lock.unlock();
};
};
t.start();
t.join(1000);
lock3.forceUnlock();
RedissonMultiLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 給lock1,lock2,lock3加鎖,如果沒有手動解開的話,10秒鐘後將會自動解開
lock.lock(10, TimeUnit.SECONDS);
lock.unlock();
client1.shutdown();
client2.shutdown();
}
5. 讀寫鎖(ReadWriteLock)
基於Redis的Redisson分散式可重入讀寫鎖RReadWriteLock
Java對象實現了java.util.concurrent.locks.ReadWriteLock
介面。其中讀鎖和寫鎖都繼承了RLock介面。
分散式可重入讀寫鎖允許同時有多個讀鎖和一個寫鎖處於加鎖狀態。
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常見的使用方法
rwlock.readLock().lock();
// 或
rwlock.writeLock().lock();
public static void main(String[] args) throws InterruptedException {
// connects to 127.0.0.1:6379 by default
RedissonClient redisson = Redisson.create();
final RReadWriteLock lock = redisson.getReadWriteLock("lock");
lock.writeLock().tryLock();
Thread t = new Thread() {
public void run() {
RLock r = lock.readLock();
// 10秒鐘以後自動解鎖,無需調用unlock方法手動解鎖
//lock.readLock().lock(10, TimeUnit.SECONDS);
r.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r.unlock();
};
};
t.start();
t.join();
lock.writeLock().unlock();
t.join();
redisson.shutdown();
}
6. 信號量(Semaphore)
基於Redis的Redisson的分散式信號量(Semaphore)Java對象RSemaphore
採用了與java.util.concurrent.Semaphore
相似的介面和用法。同時還提供了非同步(Async)、反射式(Reactive)和RxJava2標準的介面。
public static void main(String[] args) throws InterruptedException {
// connects to 127.0.0.1:6379 by default
RedissonClient redisson = Redisson.create();
RSemaphore s = redisson.getSemaphore("test");
s.trySetPermits(5);
s.acquire(3);
Thread t = new Thread() {
@Override
public void run() {
RSemaphore s = redisson.getSemaphore("test");
s.release();
s.release();
}
};
t.start();
//或
s.acquire(4);
redisson.shutdown();
}
7. 可過期性信號量(PermitExpirableSemaphore)
基於Redis的Redisson可過期性信號量(PermitExpirableSemaphore)是在RSemaphore
對象的基礎上,為每個信號增加了一個過期時間。每個信號可以通過獨立的ID來辨識,釋放時只能通過提交這個ID才能釋放。它提供了非同步(Async)、反射式(Reactive)和RxJava2標準的介面。
public static void main(String[] args) throws InterruptedException {
// connects to 127.0.0.1:6379 by default
RedissonClient redisson = Redisson.create();
RPermitExpirableSemaphore s = redisson.getPermitExpirableSemaphore("test");
s.trySetPermits(1);
// 獲取一個信號,有效期只有2秒鐘。
String permitId = s.tryAcquire(100, 2, TimeUnit.SECONDS);
Thread t = new Thread() {
public void run() {
RPermitExpirableSemaphore s = redisson.getPermitExpirableSemaphore("test");
try {
String permitId = s.acquire();
s.release(permitId);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
};
t.start();
t.join();
s.tryRelease(permitId);
}
8. 閉鎖(CountDownLatch)
基於Redisson的Redisson分散式閉鎖(CountDownLatch)Java對象RCountDownLatch
採用了與java.util.concurrent.CountDownLatch
相似的介面和用法。
public static void main(String[] args) throws InterruptedException {
// connects to 127.0.0.1:6379 by default
RedissonClient redisson = Redisson.create();
ExecutorService executor = Executors.newFixedThreadPool(2);
final RCountDownLatch latch = redisson.getCountDownLatch("latch1");
latch.trySetCount(1);
// 在其他線程或其他JVM里
executor.execute(new Runnable() {
@Override
public void run() {
latch.countDown();
}
});
executor.execute(new Runnable() {
@Override
public void run() {
try {
latch.await(550, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executor.shutdown();
executor.awaitTermination(10, TimeUnit.SECONDS);
}
Zookeeper
接著我們再看zookeeper的鎖。zk不是為高可用性設計的,但它使用**ZAB**
協議達到了極高的一致性。所以它經常被選作註冊中心、配置中心、分散式鎖等場景。它的性能是非常有限的,而且API並不是那麼好用。xjjdog傾向於使用基於**Raft**
協議的**Etcd**
或者**Consul**
,它們更加輕量級一些。我們看一下zk的加鎖時序圖。
Curator是netflix公司開源的一套zookeeper客戶端,目前是Apache的頂級項目。與Zookeeper提供的原生客戶端相比,Curator的抽象層次更高,簡化了Zookeeper客戶端的開發量。Curator解決了很多zookeeper客戶端非常底層的細節開發工作,包括連接重連、反覆註冊wathcer和NodeExistsException 異常等。Curator由一系列的模塊構成,對於一般開發者而言,常用的是curator-framework和curator-recipes,我們跳過他的其他能力,直接看分散式鎖。
1. (可重入鎖 Shared Reentrant Lock)
Shared意味著鎖是全局可見的, 客戶端都可以請求鎖。 Reentrant和JDK的ReentrantLock類似, 意味著同一個客戶端在擁有鎖的同時,可以多次獲取,不會被阻塞。 它是由類InterProcessMutex來實現。 通過acquire獲得鎖,並提供超時機制。通過release()方法釋放鎖。 InterProcessMutex 實例可以重用。 Revoking ZooKeeper recipes wiki定義了可協商的撤銷機制。 為了撤銷mutex, 調用makeRevocable方法。我們來看示例:
public class ExampleClientThatLocks {
private final InterProcessMutex lock;
private final FakeLimitedResource resource;
private final String clientName;
public ExampleClientThatLocks(
CuratorFramework client, String lockPath, FakeLimitedResource resource, String clientName) {
this.resource = resource;
this.clientName = clientName;
lock = new InterProcessMutex(client, lockPath);
}
public void doWork(long time, TimeUnit unit) throws Exception {
if (!lock.acquire(time, unit)) {
throw new IllegalStateException(clientName + " could not acquire the lock");
}
try {
System.out.println(clientName + " has the lock");
resource.use();
} finally {
System.out.println(clientName + " releasing the lock");
//釋放鎖
lock.release();
}
}
}
2. 不可重入鎖(Shared Lock)
使用InterProcessSemaphoreMutex,調用方法類似,區別在於該鎖是不可重入的,在同一個線程中不可重入。
3. 可重入讀寫鎖(Shared Reentrant Read Write Lock)
類似JDK的ReentrantReadWriteLock. 一個讀寫鎖管理一對相關的鎖。 一個負責讀操作,另外一個負責寫操作。 讀操作在寫鎖沒被使用時可同時由多個進程使用,而寫鎖使用時不允許讀 (阻塞)。 此鎖是可重入的。一個擁有寫鎖的線程可重入讀鎖,但是讀鎖卻不能進入寫鎖。 這也意味著寫鎖可以降級成讀鎖, 比如請求寫鎖 —>讀鎖 —->釋放寫鎖。 從讀鎖升級成寫鎖是不成的。 主要由兩個類實現:
InterProcessReadWriteLock
InterProcessLock
4. 信號量(Shared Semaphore)
一個計數的信號量類似JDK的Semaphore。 JDK中Semaphore維護的一組許可(permits),而Cubator中稱之為租約(Lease)。註意,所有的實例必須使用相同的numberOfLeases值。 調用acquire會返回一個租約對象。 客戶端必須在finally中close這些租約對象,否則這些租約會丟失掉。 但是, 但是,如果客戶端session由於某種原因比如crash丟掉, 那麼這些客戶端持有的租約會自動close, 這樣其它客戶端可以繼續使用這些租約。 租約還可以通過下麵的方式返還:
public void returnAll(Collection<Lease> leases)
public void returnLease(Lease lease)
註意一次你可以請求多個租約,如果Semaphore當前的租約不夠,則請求線程會被阻塞。 同時還提供了超時的重載方法:
public Lease acquire()
public Collection<Lease> acquire(int qty)
public Lease acquire(long time, TimeUnit unit)
public Collection<Lease> acquire(int qty, long time, TimeUnit unit)
主要類有:
InterProcessSemaphoreV2
Lease
SharedCountReader
5. 多鎖對象(Multi Shared Lock)
Multi Shared Lock是一個鎖的容器。 當調用acquire, 所有的鎖都會被acquire,如果請求失敗,所有的鎖都會被release。 同樣調用release時所有的鎖都被release(失敗被忽略)。 基本上,它就是組鎖的代表,在它上面的請求釋放操作都會傳遞給它包含的所有的鎖。 主要涉及兩個類:
InterProcessMultiLock
InterProcessLock
它的構造函數需要包含的鎖的集合,或者一組ZooKeeper的path。
public InterProcessMultiLock(List<InterProcessLock> locks)
public InterProcessMultiLock(CuratorFramework client, List<String> paths)
6. 完整鎖示例
我們再看一個官方完整鎖示例:
public class LockingExample {
private static final int QTY = 5;
private static final int REPETITIONS = QTY * 10;
private static final String PATH = "/examples/locks";
public static void main(String[] args) throws Exception {
// all of the useful sample code is in ExampleClientThatLocks.java
// FakeLimitedResource simulates some external resource that can only be access by one process at a time
final FakeLimitedResource resource = new FakeLimitedResource();
ExecutorService service = Executors.newFixedThreadPool(QTY);
final TestingServer server = new TestingServer();
try {
for (int i = 0; i < QTY; ++i) {
final int index = i;
Callable<Void> task = new Callable<Void>() {
@Override
public Void call() throws Exception {
CuratorFramework client = CuratorFrameworkFactory.newClient(
server.getConnectString(), new ExponentialBackoffRetry(1000, 3));
try {
client.start();
ExampleClientThatLocks example =
new ExampleClientThatLocks(client, PATH, resource, "Client " + index);
for (int j = 0; j < REPETITIONS; ++j) {
example.doWork(10, TimeUnit.SECONDS);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
e.printStackTrace();
// log or do something
} finally {
CloseableUtils.closeQuietly(client);
}
return null;
}
};
service.submit(task);
}
service.shutdown();
service.awaitTermination(10, TimeUnit.MINUTES);
} finally {
CloseableUtils.closeQuietly(server);
}
}
}
三、總結
分散式環境中,我們始終繞不開CAP理論,這也是Redisson鎖和ZK鎖的本質區別。CAP指的是在一個分散式系統中:一致性(Consistency)、可用性(Availability)、分區容錯性(Partition tolerance)。
這三個要素最多只能同時實現兩點,不可能三者兼顧。如果你的實際業務場景,更需要的是保證數據一致性。那麼請使用CP類型的分散式鎖,比如:zookeeper,它是基於磁碟的,性能可能沒那麼好,但數據一般不會丟。
如果你的實際業務場景,更需要的是保證數據高可用性。那麼請使用AP類型的分散式鎖,比如:Redisson,它是基於記憶體的,性能比較好,但有丟失數據的風險。
其實,在我們絕大多數分散式業務場景中,使用Redisson分散式鎖就夠了,真的別太較真。因為數據不一致問題,可以通過最終一致性方案解決。但如果系統不可用了,對用戶來說是暴擊一萬點傷害。
四、思考思考
以上就是我對鎖的總結和。分散式鎖不是完美的,總會有問題;如:在redis中,lockName和已有的key不能重名 !unlock的前提是lock成功!必須要設計自己的兜底方案......
回顧整個鎖界,兄弟們都掌握了哪些鎖呢?在分散式鎖的場景中都遇到哪些疑難雜症?我們評論區繼續
五、備註
redisson:
curator:
作者:京東保險 管順利
來源:京東雲開發者社區 轉載請註明來源