正常:一个请求先访问缓存,缓存里面有,取缓存里面的数据,没有去数据库查询返回,放入缓存,返回数据(图一)。
但是:当并发量大的时候,缓存中都没有,多个请求直接落在了数据库上,导致数据库异常。
代码示例:
引入redis依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.1</version>
</dependency>
model类:
public class NutUser implements Serializable {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
private String name;
@JsonIgnore
private String password;
private Integer age;
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss a", locale = "zh",timezone = "GMT+8")
private Date birthday;
@JsonInclude(JsonInclude.Include.NON_NULL)//当这个字段为空的时候是不显示给前端的
private String desc;
}
controller代码:
@RestController
@RequestMapping("/user")
public class NutUserController {
@Autowired
private NutService nutService;
@RequestMapping("/getUserJson")
public NVJSONResult getUserJson(){
//请求查询
Runnable runnable=new Runnable(){
@Override
public void run() {
nutService.findNutUser();
}
};
//多线程测试:
ExecutorService executorService =Executors.newFixedThreadPool(20);
for (int i = 0; i < 1000; i++) {
executorService.submit(runnable);
}
return NVJSONResult.ok("ok");
}
}
service层:
@Service
public class NutService {
@Autowired
private RedisTemplate<Object,Object> redisTemplate;
public NutUser findNutUser(){
//字符串的序列化
RedisSerializer redisSerializer=new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
//查询缓存
NutUser user=(NutUser)redisTemplate.opsForValue().get("user");
//插入
if (null==user){
System.out.println("数据库生成————————>");
user=create();//1.缓存为空:生成一个用户到数据库(这里生成user的操作代替查询数据库操作)
redisTemplate.opsForValue().set("user",user);//2.放入redis
}else {
System.out.println("查询缓存:");
}
//返回
return user;
}
public NutUser create(){
NutUser a=new NutUser();
a.setName("张小明");
a.setBirthday(new Date());
a.setAge(18);
String passwords = EncryptUtils.encryptPassword("nutvideo");
//System.out.println("加密:"+EncryptUtils.encryptPassword("nutvideo"));
a.setPassword(passwords);
a.setDesc("我是小明");
return a;
}
}
运行调取一下接口:
并发请求的时候,这个时候就穿透了,直接访问数据库。
解决方式:
调整service代码:
public NutUser findNutUser(){
//字符串的序列化
RedisSerializer redisSerializer=new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
//1.查询缓存
NutUser user=(NutUser)redisTemplate.opsForValue().get("user");
//2.插入
if (null==user){
//3.同步锁
synchronized (this){
//4.去redis取一下
user=(NutUser)redisTemplate.opsForValue().get("user");
if (null ==user){
System.out.println("数据库生成————————>");
user=create();//5.缓存为空:生成一个用户到数据库(这里生成user的操作代替查询数据库操作)
redisTemplate.opsForValue().set("user",user);//6.放入redis
}else{
System.out.println("查询缓存:");
}
}
}else {
System.out.println("查询缓存:");
}
//7.返回user
return user;
}
在并发编程中存在线程安全问题,主要原因有:
1.存在共享数据
2.多线程共同操作共享数据。
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块