在java中,我们可以用synchronized进行加锁,也可以用lock的lock方法加锁和unlock方法释放锁。但是在多个进程或者夸服务器的情况下,这种加锁的方式就没办法用了,因为他没办法让其他客户端的人知道是否被加锁了。所以我们可以用redis、zookeeper、etcd等来实现。redis也有类似于lock的乐观锁,在redis - 商品交易中也展示了WATCH的使用,但是当key里的内容足够多时,监控频繁的变化反而导致性能的下降。
java的map,有putIfAbsent方法,意思是如果对应的key已经赋值了,则不能继续赋值。redis中,也有类似的方法,setnx,SET if Not eXists,也是如果对应的key有值了,则不能继续赋值,所以我们可以用他这个方法作为分布式锁。
// 锁的key
static String lockName = "lock:";
static int cnt = 1000;
static CountDownLatch countDownLatch = new CountDownLatch(cnt);
@Test
public void testLock() throws InterruptedException {
JedisUtils.del(lockName);
// 锁的时间,达到这个时间就释放
int lockTime = 1;
// 锁的超时时间,达到这个时间就放弃获取
long timeOut = 2500;
for (int i = 0; i < cnt; i++) {
new Thread(new LockThread(i, lockTime, timeOut)).start();
countDownLatch.countDown();
}
TimeUnit.SECONDS.sleep(3);
}
static class LockThread implements Runnable {
int lockTime;
long timeOut;
int idx;
public LockThread(int idx, int lockTime, long timeOut) {
this.idx = idx;
this.lockTime = lockTime;
this.timeOut = timeOut;
}
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
String lock = lock(lockName, lockTime, timeOut);
if (StringUtils.isNotEmpty(lock)) {
System.out.println(idx + ":获取到了锁");
//JedisUtils.del(lockName);
}
}
}
private static String lock(String lockName, int lockTime, long timeOut) {
long end = System.currentTimeMillis() + timeOut;
String value = UUID.randomUUID().toString();
while (System.currentTimeMillis() < end) {
long setnx = JedisUtils.setnx(lockName, value);
// 1说明获取锁,返回
if (setnx == 1) {
JedisUtils.expire(lockName, lockTime);
return value;
}
try {
// 没获取则休眠100毫秒继续抢锁
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
上面的setnx和expire不是原子性的,我们可以更改:
- 通过lua语言。
- 新版本可以用
SET key value [EX seconds] [PX milliseconds] [NX|XX]
这个命令。
正常情况下,上面的分布式锁可以运行的,但是有以下几个问题:
- 设置了失效时间,如果在失效时间内没执行完,则其他进程就会获取到锁,导致同一时间有多个进程获取同一个锁。
- 如果在失效时间内获取到锁的进程已经崩溃,锁依然得不到释放,导致其他进程依然在等待失效过期。
- 如果只有一个redis服务器,那服务器崩溃则不能正常运行。如果redis做主从, A进程获取锁,且在主服务器数据未同步到从服务器的时候崩溃,B进程读取已经变成主服务器的原从服务器时,因为没有锁又获取到了锁,导致同一时间有多个进程获取同一个锁。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。