高并发无锁情况下,将存在缓存不一致情况
实验逻辑
- 查询操作
- 如果 sql+参数 在map缓存中存在与之对应的value,则直接返回该value
- 如果不存在,则查询数据库,并将 sql+参数 作为key,查询结果作为 value 放入map缓存中
- 修改操作:直接对数据库进行修改,然后清空缓存map
使用读写锁提高并发读,读-读可并发
实现代码
Person :实体类
//Person 实体类
class Person{
int id;
String name;
Person(int id,String name){
this.id=id;
this.name=name;
}
public int getId(){
return id;
}
public String getName(){
return name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
PersonDao:无缓存的Person数据库操作对象
/**
* 无缓存的Person数据库操作对象
*/
class PersonDao{
static String URL="jdbc:mysql://localhost:3306/lock_test?serverTimezone=UTC";
static String USERNAME="root";
static String PASSWORD="123456";
//查询操作
public Person selectOne(String sql,Object... args) throws SQLException, ClassNotFoundException {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接
try(Connection conn=DriverManager.getConnection(URL,USERNAME,PASSWORD)){
//创建PreparedStatement对象
try(PreparedStatement pst=conn.prepareStatement(sql)){
//设置sql参数
if (args!=null){
for (int i = 0; i < args.length; i++) {
pst.setObject(i+1,args[i]);
}
}
System.out.println("执行查询语句"+sql);
//执行sql并获取结果
ResultSet resultSet = pst.executeQuery();
while(resultSet.next()){
return new Person(resultSet.getInt("id"), resultSet.getString("name"));
}
}
}
return null;
}
//修改操作
public int update(String sql,Object... args) throws ClassNotFoundException, SQLException {
//加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接
try(Connection conn=DriverManager.getConnection(URL,USERNAME,PASSWORD)){
//创建PreparedStatement对象
try(PreparedStatement pst=conn.prepareStatement(sql)){
//设置sql参数
if (args!=null){
for (int i = 0; i < args.length; i++) {
pst.setObject(i+1,args[i]);
}
}
System.out.println("执行修改语句"+sql);
//执行sql并获取结果
return pst.executeUpdate();
}
}
}
}
PersonDaoCache:带缓存的Person数据库操作对象
/**
* 带缓存的Person数据库操作对象:继承PersonDao,装饰PersonDao对象的方法实现缓存
*/
class PersonDaoCache extends PersonDao{
//创建无缓存的Person数据库操作对象
private PersonDao pd=new PersonDao();
//缓存map集合
HashMap<Sqlkey,Person> map=new HashMap<Sqlkey, Person>();
//读写锁
ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();
//查询操作
public Person selectOne(String sql, Object... args) throws SQLException, ClassNotFoundException {
//获取读锁
reentrantReadWriteLock.readLock().lock();
//创建缓存key对象
Sqlkey key=new Sqlkey(sql,args);
try{
//判断是否已存在缓存数据
if (map.containsKey(key)){
return map.get(key);
}
}finally {
//释放读锁
reentrantReadWriteLock.readLock().unlock();
}
//获取写锁
reentrantReadWriteLock.writeLock().lock();
try {
//考虑多线程环境下存在多个请求先后进入该逻辑:在第一个逻辑执行后存在缓存的情况下,后面的请求直接读取缓存数据
if (map.containsKey(key)){
return map.get(key);
}
//不存在缓存,查询数据库并设置缓存
Person person = pd.selectOne(sql, args);
map.put(key,person);
return person;
}finally {
//释放写锁
reentrantReadWriteLock.writeLock().unlock();
}
}
//修改操作
public int update(String sql, Object... args) throws ClassNotFoundException, SQLException {
//获取写锁
reentrantReadWriteLock.writeLock().lock();
try {
//修改数据库并清空缓存
int update = pd.update(sql, args);
map.clear();
return update;
}finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
//缓存key对象:封装sql和args参数数组
class Sqlkey{
private String sql="";
private Object[] orgs;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Sqlkey key = (Sqlkey) o;
return Objects.equals(sql, key.sql) && Arrays.equals(orgs, key.orgs);
}
@Override
public int hashCode() {
int result = Objects.hash(sql);
result = 31 * result + Arrays.hashCode(orgs);
return result;
}
public Sqlkey(String sql, Object[] orgs) {
this.sql = sql;
this.orgs = orgs;
}
}
}
测试
代码
public class ReentranReadLockDemo {
static PersonDao pd=new PersonDaoCache();
@Test
public void demo1() throws SQLException, ClassNotFoundException {
//第一次查询数据库
System.out.println(pd.selectOne("select * from person where id=?", 1));
//查询缓存
System.out.println(pd.selectOne("select * from person where id=?", 1));
//查询缓存
System.out.println(pd.selectOne("select * from person where id=?", 1));
//修改数据库,清空缓存
pd.update("update person set name='hhh' where id=?",1);
//缓存被清空。查询数据库
System.out.println(pd.selectOne("select * from person where id=?", 1));
}
}
输出
执行查询语句select * from person where id=?
Person{id=1, name='修改前'}
Person{id=1, name='修改前'}
Person{id=1, name='修改前'}
执行修改语句update person set name='修改后' where id=?
执行查询语句select * from person where id=?
Person{id=1, name='修改后'}
注意
-
以上实现体现的是读写锁的应用,保证缓存和数据库的一致性,但有下面的问题没有考虑
-
适合读多写少,如果写操作比较频繁,以上实现性能低
-
没有考虑缓存容量
-
没有考虑缓存过期
-
只适合单机
-
并发性还是低,目前只会用一把锁
-
更新方法太过简单粗暴,清空了所有 key(考虑按类型分区或重新设计 key)
-
-
乐观锁实现:用 CAS 去更新