并发编程下的锁机制,乐观锁、悲观锁、共享锁、排他锁、分布式锁、锁降级原理篇
一、悲观锁
比较悲观,担心拿数据时被别人修改,所以查询时先加锁在修改,保证操作时别人修改不了,期间需要访问该数据的都会等待。
select version from user where id=1 for update
update user set version=2 where id=1
在对id = 1的记录修改前,先通过for update的方式进行加锁,然后再进行修改。这就是比较典型的悲观锁策略。
1.共享锁
又称为读锁,可以查看但无法修改和删除的一种数据锁。(读取)操作创建的锁。其他用户可以并发读取数据,
但不能修改,增加,删除数据。资源共享。
select name from user where id=1 lock in share mode
2.排他锁
又称为写锁,其他线程对该记录的更新与删除操作都会阻塞等待。
select version from user where id=1 for update
二、乐观锁
比较乐观,每次拿数据的时候都完全不担心会被别人修改,所以不会上锁,但是在更新数据的时候去判断该期间是否被
别人修改过(使用版本号等机制,靠表设计和代码来实现)
select version from user where id=1
result version=1
update user set version=2 where id=1 and version=1
我们在更新之前,先查询一下库存表中当前版本号,然后在做update的时候,以版本号作为一个修改条件,
当我们提交更新的时候,判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对,如果数据
库表当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。
悲观锁:用于写比较多的情况,避免了乐观锁不断重试从而降低性能
乐观锁:用于读比较多的情况,避免了不必要的加锁的开销
三、CAS与synchronized
CAS属于乐观锁,适用于写比较少的情况,冲突较少
synchronized属于悲观锁,适用于冲突写比较多的情况,
冲突较多竞争较少的场景:synchronized会阻塞和唤醒线程并在用户态和内核态切换浪费消耗cpu资源。
CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
竞争严重的场景:CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
阿里巴巴java开发手册:如果线程访问冲突小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不小于3次。
四、分布式锁
synchronized 只是本地锁,锁的也只是当前jvm下的对象,在分布式场景下,要用分布式锁。
package com.kero99.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
@RestController
@Component
public class RedisLock {
/**
* @author ygc
* jMeter 线程组工具
* 压力测试工具 http://coolaf.com/tool/testing
*/
//redis连接池 old 高并发场景会挂掉
// Jedis redis = RedisPool.getJedis();
//now
@Autowired
private RedisOperator redis;
//死锁 超时时间 加锁 业务逻辑 释放锁
@RequestMapping("/redisLock")
public String redisLock() {
//synchronized 只是本地锁,锁的也只是当前jvm下的对象,在分布式场景下,要用分布式锁。
// synchronized(this) {
//redis分布式锁
// String uuid=UuidUtil.getUUID32();
// Long lock=redis.setnx("lock", uuid);
// redis.expire(String.valueOf(lock), 30);
// try {
// if(lock<=0) {
// return "error";
// }
System.out.println(redis.get("stock"));
int stock =Integer.parseInt(redis.get("stock"));
if(stock>0) {
stock=stock-1;
redis.set("stock", stock+"");
System.out.println("扣减成功,库存stock"+stock);
}else {
System.out.println("减免失败 库存不足"+stock);
}
// } catch (Exception e) {
// e.printStackTrace();
// }finally {
// //高并发场景 线程安全保证原子性操作
// if(uuid.equals(redis.get("lock"))) {
// //出现异常报错
// redis.del("lock"); //释放锁
// }
// }
// }
return "end";
}
}
五、锁降级
锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
package com.kero99.utils;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 锁降级
* @author ygc
*
*/
public class Test1 {
public static void main(String[] args) {
lockData();
}
public static void lockData() {
boolean update=false;
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
Lock writeLock = reentrantReadWriteLock.writeLock();
Lock readLock = reentrantReadWriteLock.readLock();
readLock.lock();
//update为false时未更新
if(!update) {
//必须先释放读锁
readLock.unlock();
writeLock.lock();
try {
if(!update) {
//数据流程
System.out.println("更新数据处理..");
//更新完成
update=true;
}
readLock.lock();
}finally {
writeLock.unlock();
}
//锁降级完成,写锁降级为读锁
}
try {
//数据流程
System.out.println("读取数据处理..");
} finally {
// TODO: handle finally clause
readLock.unlock();
}
}
}