前言:spring默认是单例模式,这句话大家应该都不陌生;因为绝大多数都是使用单例模式,避免了某些问题,可能导致对某些场景缺乏思考。本文通过结合lock锁将单例模式、静态变量、锁对象等知识点串联起来。
文章目录
- synchronized与ReentrantLock
- 静态变量
- spring与ReentrantLock整合
- 生产环境项目加锁方案
synchronized与ReentrantLock
这两种锁,提得最多的两点区别:
1.效率问题
2. ReentrantLock可以设置超时时间
当然,还有其它区别 例如ReentrantLock支持公平锁 本文不展开重点讨论,本文重点从第一点延伸。
关于效率问题,主要是旧版jdk6之前 锁很重 ,在jdk6之后优化了很多;可能有同学会说synchronized锁的范围太大了,ReentrantLock锁的范围小,但其实只要我们自己定义一个锁对象即可:
Object lock = new Object();
public void test(){
// do something
synchronized(lock){
// do something
}
// do other
}
从上面代码我们可以看到有个重要的概念:锁对象,通俗点理解就是根据什么对象来判断是否加上了锁。
再看看ReentrantLock的写法:
ReentrantLock lock = new ReentrantLock();
public void test(){
// do something
lock.lock();
// do other
lock.unlock();
}
不难发现,ReentrantLock并没有显式的声明锁对象;其实就是lock对象本身,锁的是它自己。
正因为lock.lock() 是锁它本身,所以我们需要思考,当lock对象发生了变化,锁是否还能达到我们的预期。
静态变量
简单复习一下:静态变量存放于jvm的方法区,它只会执行一次,是共享变量。
spring与ReentrantLock整合
我们假设业务场景为:同一时刻 只允许一个用户下单,且为单体项目(即不考虑集群多实例)
简单看一下demo代码:思考从前端/浏览器发起多次请求,打印结果是否一致?
@RestController
@RequestMapping("/test/single")
public class SingleController {
private Lock lock = new ReentrantLock();
@GetMapping
public void test() {
System.out.println(lock.hashCode());
}
}
答案是一致,因为spring默认是单例模式,所以每次访问的SingleController bean ,都为同一个,bean没重新创建,里面的属性(demo中的lock对象)自然也都是同一个。
如果实在担心hashCode不严谨,或者可能是hash碰撞问题,那我们可以加锁,看多次请求浏览器是不是在等待响应也可以得到相应的结论。
如下代码,超时时间设为10秒
观察浏览器后续请求是否在等待响应(等待锁释放)
我们再来看看反面教材:
@RestController
@RequestMapping("/test/single")
public class SingleController {
@GetMapping
public void test() {
Lock lock = new ReentrantLock();
lock.lock();
System.out.println("下单---");
// 省略try catch finally
lock.unlock();
}
}
这把锁有作用吗?答案是否定的,因为每次进入到方法里面,生成的都是新对象,锁对象也都变了。
习惯了spring的单例模式,那接下来我们再手动把spring的模式改成多例会发生什么呢:
@RestController
@RequestMapping("/test/single")
@Scope("prototype")
public class SingleController {
private Lock lock = new ReentrantLock();
@GetMapping
public void test() {
System.out.println(lock.hashCode());
}
}
这把锁还有用吗?答案是否定的,因为每次浏览器发起请求,SingleController 是不同的对象,那lock自然也是不同的对象,所以失去了锁的作用。
那么如果在多例模式下,还想锁生效该怎么办呢?前文提到了静态变量的概念,静态变量初始化后就不会改变(除非我们手动改),那只要将锁对象修饰为static,即可达到效果:
@RestController
@RequestMapping("/test/single")
@Scope("prototype")
public class SingleController {
private static Lock lock = new ReentrantLock();
@GetMapping
public void test() {
System.out.println(lock.hashCode());
}
}
本文通过一个简单的案例,将锁对象和spring单多例知识点串联,巩固java基础。
生产环境项目加锁方案
在实际的企业项目中,都会集群部署,所以我们需要引入分布式锁,如redis锁,可以基于数据量考虑手写redis锁,或引入redisson;具体案例分析可以在博主首页搜索“超卖”