原互斥锁+双端检查机制
方案
- 原方案中,
synchronized
的锁对象为UserServiceImpl.class
(即当前接口实现类) - 该方案中,不论查询的key是什么,
synchronized
锁对象都为UserServiceImpl.class
public UserInfo getUserByIdFirstRedis(Integer id) {
UserInfo userInfo;
String key = "user_" + id;
userInfo = (UserInfo) redisTemplate.opsForValue().get(key);
if (null == userInfo || userInfo.getId() == 0) {
synchronized (UserServiceImpl.class) {
userInfo = (UserInfo) redisTemplate.opsForValue().get(key);
if (null == userInfo || userInfo.getId() == 0) {
System.out.println(Thread.currentThread().getName() + " =》实际请求MySQL");
userInfo = userDao.getById(id);
redisTemplate.opsForValue().set(key, userInfo);
}
}
}
return userInfo;
}
互斥锁+双端检查机制
方案的优化
点
- 优化点:降低
synchronized
的锁对象,即减小锁的粒度
- 原方案中,
synchronized
锁对象是UserServiceImpl.class
,故不论哪个key
进来,都需要排队等待锁 - 优化后,
synchronized
锁对象优化为缓存key
,相同key
排队等待锁,不同key
可以并发请求更新redis数据,提高了并发度 - 难点:如何让每个请求进来,同样的变量参数,能够获取到同一个
key锁对象
?
- 方案:利用字符串常量池,将参数变量生成的
key
放到字符串常量池中,后续只要key相同,都会返回常量池中的同一个String对象
public UserInfo getUserByIdFirstRedis(Integer id) {
UserInfo userInfo;
String key = ("user_" + id).intern();
userInfo = (UserInfo) redisTemplate.opsForValue().get(key);
if (null == userInfo || userInfo.getId() == 0) {
synchronized (key) {
userInfo = (UserInfo) redisTemplate.opsForValue().get(key);
if (null == userInfo || userInfo.getId() == 0) {
System.out.println(Thread.currentThread().getName() + " =》实际请求MySQL");
userInfo = userDao.getById(id);
redisTemplate.opsForValue().set(key, userInfo);
}
}
}
return userInfo;
}
测试
- 2个key,同时发出1000个线程请求,对比两个方案的执行时间
- 同时检测优化后的方案是否会出现
锁不住
情况
public void getUserByIdFirstRedis() {
CountDownLatch count1 = new CountDownLatch(1000);
CountDownLatch count2 = new CountDownLatch(1000);
long s = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
userService.getUserByIdFirstRedis(1);
count1.countDown();
}, "t1::" + i).start();
}
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
userService.getUserByIdFirstRedis(2);
count2.countDown();
}, "t2=》" + i).start();
}
try {
count1.await();
count2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("总耗时 =》" + (System.currentTimeMillis() - s));
}
时间对比
synchronized (UserServiceImpl.class)
执行时间1204
毫秒
synchronized (key)
执行时间993
毫秒
synchronized (key)
是否能锁住
- 从上面中可以看出,
synchronized (UserServiceImpl.class)
只有2个线程请求到了MySQL - 而
synchronized (key)
也是只有2个线程请求到了MySQL,故能锁住