1.直接在方法上加synchronized
public synchronized User getUserById(Integer id) {
// 设置redis的key按照String进行序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 查询redis缓存中是否存有数据
User user = (User) redisTemplate.opsForValue().get("userKey-" + id);
// redis缓存中没有该学生信息
if (user == null) {
// 从数据库中查询,在存储到redis缓存中
user = userMapper.selectByPrimaryKey(id);
redisTemplate.opsForValue().set("userKey-"+ id, user);
System.out.println("查询了数据库。。。");
} else {
System.out.println("查询了缓存。。。。");
}
return user;
}
以上方法的弊端
该方法将每次调用getUserById方法的用户都进行加锁,这意味着,每次有且只能有一个用户进行查询缓存和查询数据库两个操作之一。
理想状态
当十个线程进来调用时,都可以查询redis缓存,让一个进来的线程去查询数据库,其他线程等待,该线程执行完后,其他九个线程再次执行查询缓存。
2.双重检查锁
@Override
public User getUserById(Integer id) {
// 设置redis的key按照String进行序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 查询redis缓存中是否存有数据
User user = (User) redisTemplate.opsForValue().get("userKey-" + id);
// redis缓存中没有该学生信息
if (user == null) {
// 从数据库中查询,在存储到redis缓存中
synchronized (this) {
// 再查询一次redis缓存中是否存有该user数据
user = (User) redisTemplate.opsForValue().get("userKey-" + id);
//依旧没有数据,则说明是第一次,进行查询数据库
if(user == null) {
user = userMapper.selectByPrimaryKey(id);
redisTemplate.opsForValue().set("userKey-" + id, user);
System.out.println("查询了数据库。。。");
}
}
} else {
System.out.println("查询了缓存。。。。");
}
return user;
}
以上代码解读
该方式,当第一波并发进行访问时,比如10个线程,10个查询redis缓存中,发现没有缓存。这时,user为null。在synchronized代码块中,一次只能执行一个线程。第一个线程获得锁时,发现redis缓存中还是没有数据,则去查询数据库,将查询的数据存储在redis缓存中。其他线程等啊等啊等。第一个线程终于结束了。其他九个线程再获得锁时,发现线程中有数据了,老子不干了。就可以大大的减少一个一个线程的执行了。