在代碼世界游走,沒幾把“鎖”防身可不行

来源:https://www.cnblogs.com/Jcloud/archive/2023/08/21/17645228.html
-Advertisement-
Play Games

鎖共有多種演算法,在併發場景中都是被常常用到,想必大家都已爐火純青般.....巴特!我們還有後浪同學們可能不熟悉,那我在這裡聊下鎖的用法和使用場景。 ...


一、開篇背景

“鎖”代表安全。在程式中(這裡指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:

curator的ZK客戶端示例

作者:京東保險 管順利
來源:京東雲開發者社區 轉載請註明來源


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 為什麼需要設備驅動模型 內核版本發展 2.4版本之前內核沒有統一的設備驅動模型,但是可以用(例如先前的led字元設備驅動實驗,使用前需要手動調用mknod命令創建設備文件,從而進一步控制硬體)。 2.4~2.6版本內核使用devfs,掛載在/dev目錄。需要在內核驅動中創建設備文件(調用devfs_ ...
  • ![](https://img2023.cnblogs.com/blog/3076680/202308/3076680-20230821142549307-1962607264.png) # 1. 命令行工具 ## 1.1. mysql -u root -p; ## 1.2. mysql> show ...
  • # 前言 在現代信息系統中,數據是至關重要的資產之一。作為一名後端開發人員,與資料庫的交道必不可少,為了確保數據的完整性、一致性和可靠性,資料庫引入了事務的概念。本次將帶您深入瞭解資料庫事務的重要性、特性以及如何在應用程式中正確地使用事務來維護數據的穩定性。 ## 什麼是資料庫事務? 資料庫事務是一 ...
  • ![file](https://img2023.cnblogs.com/other/2685289/202308/2685289-20230821163010585-15599264.png) DolphinScheduler是一個開源的分散式任務調度系統,擁有分散式架構、多任務類型、可視化操作、分 ...
  • 近日全球領先的IT市場研究和咨詢公司IDC正式發佈《中國關係型資料庫軟體市場跟蹤報告-數據倉庫市場Add-on》報告華為雲GaussDB(DWS)憑藉領先的技術和優異的市場表現榮獲“雙第一”。 ...
  • 最近因需求改動新增了一些資料庫表,但是在定義表結構時,具體列屬性的選擇有些不知其所以然,索引的添加也有遺漏和不規範的地方,所以我打算為創建一個高性能表的過程以實戰的形式寫一個專題,以此來學習和鞏固這些知識。 ...
  • NineData與實時數據倉庫廠商SelectDB完成產品相容互認證,實現了軟體相互相容、功能完善、運行穩定且性能優異。雙方將持續助力數據管理與大數據分析業務的融合,幫助企業實現數字化轉型,提高效率、降低成本,並滿足客戶業務需求。SelectDB成立於2022年初,團隊來自知名互聯網和雲計算公司,核... ...
  • # 前言 本篇來介紹一下redis pipeline,主要是由於最近一次在幫開發同學review代碼的時候,發現對redis有個迴圈操作可以優化。場景大概是這樣的,根據某個uid要從redis查詢一批數據,每次大概1000個key左右,如果查得到就返回,否則查db,然後寫回緩存。由於每次要查的key ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...