一直以来想写一篇关于锁的总结,由于java的锁太多了,五花八门,而且有时候不知道怎么去选择,好多人都犯糊涂,固然本身也犯糊涂。本章并非介绍java中的concurrent包下的相关锁,那个通常在开发中间件系统中经常使用,更多的人开发是在和数据库打交道,接触的都是数据库级别的锁。废话很少说,直接开始。java
一、悲观锁redis
悲观锁在数据库中,通常都是这样的select ... from xxx where id = xxx for update数据库
这种锁,一上来就直接锁住了一条语句,并发效率极其差,若是业务代码处理稍微复杂一点基本上数据库很快就挂了,会报一大堆cant get connect ,这种确定不会用的。安全
二、乐观锁并发
这种锁有不少公司都在用,好比说京东,通常来讲分为两步。分布式
第一步,select ... from xxx where id = xxx高并发
第二步,update xxx set ... where id = xxx and xxx=....工具
说白了就是先去捞取数据,而后带上条件去更新。若是此时产生并发,那么更新就会只有一个成功,其余的都是失败,也就是返回一个0。这时候我能够选择从新查询继续尝试,或者直接返回异常。.net
三、分布式锁(redis)线程
这个锁其实通常都是用redis来作的,算是悲观锁的变种,可是好一点是锁内存,只要共用一个redis集群的系统均可以对其可见。也有不少公司喜欢用,好比说网易。通常来讲也是分为两步。
第一步,jedis.set(key, value, "NX", "EX", seconds)
第二步,jedis.del(keys)
首先,先设置一个key,而后赋值value,而且设置超时时间。若是此时出现并发,那么只要key没有被删掉,那么只有一个线程会返回true,其余的都返回false,这就能够作相应的处理。可是必定要注意,要删除jeids的key,这个好多人都会犯错误,我也犯过好屡次。我想此时,会有小伙伴说,加上finally啊,在finally里面删除。我一开始也是这么想的,后来发现犯了一个很是严重的错误,由于redis的锁毕竟不是真正的锁,它任什么时候候都是返回值。假设一个用户进入锁了,没有释放,另外一个用户进来返回false,那也会执行finally块,这样的话锁就没意义了。因此这也是比较坑的地方,而且代码风格很是很差,而且内存当作锁很是不安全,可是相比1,2两种锁已经很不错了。
四、支付宝的经常使用锁
在介绍支付宝的锁以前先说一件事,我在加入支付宝以前,也对它的锁感兴趣,一直在想究竟支持如此高并发的场景的公司的锁到底长什么样子。因此当我刚到公司就火烧眉毛的看源代码,支付宝关于锁的处理作成了一个工具类,很方便你们调用,里面只用了不到50行代码实现一个锁。
支付宝的锁的核心是悲观锁!是的,你没有听错,我看到之后很不解,我问了下咱们组一个特别吊的大牛---司令。我问他为何要这样实现?若是说用乐观锁不是更好么?他问我你要是用乐观锁你怎么处理?先查而后更新,而后不匹配你怎么办?我说就抛异常啊!他说,好的,那么若是说你的业务比较复杂,执行到这一步花费了不少时间呢?你并非想当即抛异常,而是但愿重试几回呢?我说这个能够用一个for循环,更新失败就sleep,重试几回。他说,能够,那么若是你用成千上万个线程呢?这时候都在for,你的数据库是否是挂了?我那时恍然大悟。那么支付宝的锁到底怎么实现的呢?
b = false;
for(重试次数) {
try {
select .. from xx where id = xxx for update nowait;
获取锁成功,b=true; 直接break;
} catch() {
异常捕获;
}
sleep(ms)
}
if(!b) {
throw new Exception(xxxx);
}
他给我解释到:首先获取锁,由于有nowait关键字,那么锁会当即返回,若是没有获取到,在并发的时候会抛异常,而后证实抢锁激烈那么就sleep几毫秒,而后重试抢锁,若是抢到了就直接跳出循环。若是重试次数超过必定次数,那么证实此时抢锁特别激烈,那么直接抛异常。
他说,咱们要知道数据库的链接是很是宝贵的,在用完之后必定要及时回收,抛异常是最快的回收连接方式,因此,支付宝的代码有不少的异常种类。虽然乐观锁看起来很清新,可是它长时间占用着连接且在开发复杂业务有个弊端,每每写代码的人都没法分辨何时该抛异常,致使将业务代码处理的很乱,常常形成死锁。这样看来,带nowait的悲观锁真的是很好,而且配合上事务也不用像redis那样本身要删除锁,致使常常出错,并且比较数据库级别的锁比内存稳定多了啊!
这就是本章介绍的几种锁,但愿能给你们带来些收获!