目录
先看一个测试案例 微服务在后面!!!
应用场景:
数据量小 访问频率高 数据变化频率低(数据不一致)
01 测试代码:(放到后面, 主要看效果)
(模拟多线程并发的安全问题)
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
Thread t0 = new Thread() {
@Override
public void run() {
System.out.println(selectAll());
}
};
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println(selectAll());
}
};
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println(selectAll());
}
};
Thread t3 = new Thread() {
@Override
public void run() {
System.out.println(selectAll());
}
};
Thread t4 = new Thread() {
@Override
public void run() {
System.out.println(selectAll());
}
};
Thread t5 = new Thread() {
@Override
public void run() {
System.out.println(selectAll());
}
};
t0.start();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
02 测试功能代码
(模拟jvm的本地缓存)
private static List<String> cache = new ArrayList<>();
private static List<String> selectAll() {
if (cache.isEmpty()) {
System.out.println("======>IMITATION GET DATA FROM DATABASE <=======");
List<String> cates = Arrays.asList("CateGory-A", "CateGory-B", "CateGory-C");
cache.addAll(cates);
}
}
解释: 建立一个变量 如果这个list是空的 从数据库提取 不是空的直接用
第一个判断条件(是否开启本地缓存[通过配置中心拉取的配置数据进行判断],通常不开启,数据不一致)
进行测试: -------------------->
开启使用上面代码的本地缓存遇到的问题:
/*
* 模拟多线程情况下的本地缓存问题
* 1. 线程异常错误 java.util.ConcurrentModificationException
* 2. 查库次数过多 正常只查一次
* 3. 数据list多次拼装, 数据异常
* 正常: [CateGory-A, CateGory-B, CateGory-C]
* 异常: [CateGory-A, CateGory-B, CateGory-C, null, null, null, CateGory-A, CateGory-B, CateGory-C, CateGory-A, CateGory-B, CateGory-C]
*/
异常问题修改
修改方案一:(不推荐)
private static List<String> cache = new Vector<>();
// 使用vector 集合 底层采用悲观锁的方式 一个更新其他必须等待 (不建议,性能差)
修改方案二: ( jdk1.5推出,推荐)
private static CopyOnWriteArrayList<String> cache = new CopyOnWriteArrayList<>();
//乐观锁 不会有线程异常了 但是数据依然存在重复(推荐)
乐观锁底层原理 -> CAS算法
自己的理解: -> CAS(比较和交换)cpu硬件算法 内存中的数与原来的数字进行比较 没有变化,将新的值交换给原来的值,不然就取消 这块百度吧, 自己理解的
数据添加错误(重复添加修复)
修改方案一:
private synchronized static List<String> selectAll() {
if (cache.isEmpty()) {
System.out.println("======>IMITATION GET DATA FROM DATABASE <=======");
List<String> cates = Arrays.asList("CateGory-A", "CateGory-B", "CateGory-C");
cache.addAll(cates);
}
//模拟数据库请求数据, 添加到本地缓存中
return cache;
}
性能差 悲观锁 一个进来其他阻塞(不推荐)
修改方案二:
//方法02 优化 加一个校验
private static List<String> selectAll() {
if (cache.isEmpty()){
synchronized (cache){//同步代码块
if (cache.isEmpty()) {
System.out.println("======>IMITATION GET DATA FROM DATABASE <=======");
List<String> cates = Arrays.asList("CateGory-A", "CateGory-B", "CateGory-C");
cache.addAll(cates);
}
}
}
//模拟数据库请求数据, 添加到本地缓存中
return cache;
}
添加一个同步代码块
/**
* 问: cache.isEmpty()为何要再次校验???
* 答: 假设abcd 四个值 ab拿到的值都是空,--
* --> 遇到锁后排队 然后依旧多次查库,所以再次校验
* 避免多次查库的情况发生
*/
查看测试结果: 成功!!! 写一个好代码真难
main
======>IMITATION GET DATA FROM DATABASE <=======
[CateGory-A, CateGory-B, CateGory-C]
[CateGory-A, CateGory-B, CateGory-C]
[CateGory-A, CateGory-B, CateGory-C]
[CateGory-A, CateGory-B, CateGory-C]
[CateGory-A, CateGory-B, CateGory-C]
[CateGory-A, CateGory-B, CateGory-C]
微服务 使用场景
@RefreshScope下 这个注解
@Value("${useLocalCache:false}")
private boolean useLocalCache;
当并发量大的时候选择开启 不需要的时候关闭
注: 微服务其他细节 这里不做说明 , 这里为了突出jvm本地缓存
redis分布式缓存这里不做说明
[难点: 多线程并发的安全问题]