开发中锁的简单实现
- 业务需求场景:多个用户共同抢票,多个线程共同使用某个资源。
- 问题分析:如果多个线程对某个共有资源进行操作的时候,如果不对资源进行上锁,那么就会出现超卖,线程之间不同步的问题,处理这个问题有多种方式。
乐观锁
CAS(Compare-and-Swap)
比较并替换。读到某个值,对它进行更新之前,检查原值是否变化了,如果发生变化那么将原值更新为变化后的值。这两步骤是原子性的。乐观锁不是锁,而是一个在循环里尝试CAS的算法。
Juc下的原子相关的操作是乐观锁,
悲观锁
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的 锁。例如synchronized
、Lock
锁
重入锁
是以线程为单位,当一个线程获取对象锁之后,这个线程可以再 次获取本对象上的锁,而其他的线程是不可以的。
使用RedissonClient API实现分布式锁
redisson
内部提供了监控锁的进程 ,可以通过设置key
的有效时间进行防止死锁的问题,在加锁的时候应该考虑锁的颗粒度要小,范围尽量小。
- 获得锁
public boolean tryGetLock(String lockKey, String requestId, int expireTime) {
// 获取一把锁
RLock lock = redissonClient.getLock(lockKey);
try {
// 加锁
lock.lock();
logger.info("=====> "+lockKey+"加锁成功");
return true;
} catch (Exception e) {
logger.error("=====> 出错:"+e.getMessage());
}
return false;
}
- 释放锁
public boolean tryRelaseLock(String lockKey){
try {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
logger.info("=====> "+lockKey+"释放锁成功!");
return true;
}catch (Exception e){
logger.info("=====> "+lockKey+"释放锁失败!");
return false;
}
}
- 测试代码
@RequestMapping("/test4")
public String test4(@RequestParam String name) {
try {
boolean flag = tryGetLock("ticketnum", null, 10);
if(!flag){
return "当前资源正在被使用请排队...";
}
RBucket<Ticket> ticket = redissonClient.getBucket("ticket");
Ticket tickets = ticket.get();
Integer num = tickets.getNum();
logger.info("=====> 当前总票数为:"+num);
if(num > 0){
try{
Thread.sleep(3000);
}catch (Exception e){
logger.error(e.getMessage());
}
num = num-1;
tickets.setNum(num);
redissonClient.getBucket("ticket").set(tickets);
logger.info("=====> 当前票的库存为:"+num);
}else{
logger.info("=====> 票已经抢完了...");
}
return "抢票操作完成";
} catch (Exception e) {
return e.getMessage();
} finally {
tryRelaseLock("ticketnum");
}
}
JDK synchronized锁
在加了它的方法、代码块中,一次只允许一个线程进入特定代码段,从而避免多线程同时修改同一数据。在synchronized
代码语句块内操作时原子的,一个资源在一个时刻只能允许一个线程持有该资源,并且进行操作它。
synchronized
加锁之后,其他获取该资源的线程会被阻塞。当代码退出或者抛出异常的时候,会释放锁对象。
-
修饰方法的时候,当前对象作为锁的对象。
-
修饰类的时候,默认是当前类的Class对象为锁的对象。
-
测试代码:当有多个请求进来的时候,第一个线程请求得到锁的对象,别的线程都被阻塞了,等到第一个线程执行结束后,其他线程一次加锁执行。
@RequestMapping("/locktest")
public synchronized void test1() {
for (int i = 0; i < 10; i++) {
dealNum();
}
System.out.println("======>" + num1);
System.out.println("======>" + num2);
num1 = 0;
num2 = 0;
}
JDK Lock的使用
Lock锁的释放需要显示的进行释放,如果不释放资源会发生死锁问题。
/**
* 多个线程使用同一个锁
*/
private ReentrantLock lock = new ReentrantLock();
@Override
public void test11() {
logger.info("doOne()执行前...");
doOne();
logger.info("doOne()执行后...");
}
private void doOne() {
lock.lock();
try {
logger.info("doOne()执行中...");
Thread.sleep(6000);
} catch (Exception e) {
} finally {
lock.unlock();
}
}
- Lock接口的实现
ReentrantLock
锁,分为公平锁和非公平锁。公平锁,线程按照申请锁的顺序去执行,非公平锁会直接去尝试获取锁,如果获取不到那么会放入队列中。简单理解就是排队的时候插队和不插队的问题。
// 默认无参构造函数 非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}