共计 15222 个字符,预计需要花费 39 分钟才能阅读完成。
一、开篇背景
“锁”代表平安。在程序中(这里指 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 客户端示例
作者:京东保险 管顺利
起源:京东云开发者社区 转载请注明起源