问题提出:缓存对象取出数据时出现的线程不安全问题?
昨天写了FIFO和LRU以及日志的缓存对象,这些缓存对象都是用来缓存数据的,但是会出现一个问题。我们提供了缓存数据的对象后,用户在缓存数据后,想要将数据取出时,并不是真的将数据对象取出,而是取出一个对象的引用。真正的数据对象还是存在在内存中的,我们只是引用了它,这个时候就出现问题了,当多个用户同时操作这个数据对象时,就会出现线程不安全的状态。
为什么会出现这种状态?
其实就是因为,多个用户同时共享一个数据对象时,同时都引用这一个数据对象。如果有用户修改了这个数据对象,那么其他用户拿到的就是已经修改过的对象,这样就是出现了线程不安全。
如何处理这个问题?
解决办法
两种方法:第一种方法就是加锁,但是这样就会导致,一个用户在操作对象时,其他用户只能等着,这样设计,会影响性能。第二种方法就是通过对象的序列化和反序列化来实现。
我们知道,对象在实现了序列化接口之后就可以被序列化成字节来存储,而在反序列化的时候,是将字节在反序列化成原来的数据。注意这里是数据,只是数据一样而已,对象已经不是原来的对象了。因为字节在被反序列化时,会在创建一个新的对象,这个新的对象的数据和原来对象的数据一模一样。
所以这样有了序列化和反序列化的设计,就解决了线程不安全的问题。用户在添加或者取出数据时,我们让数据对象经过SerializedCache的序列化和反序列化处理之后。取出时都会取出一个新的对象。
上代码
问题已经分析完了,接下来直接上代码。
package com.test.prictice;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializedCache implements Cache{
private Cache cache;
public SerializedCache(Cache cache) {
this.cache=cache;
}
@Override
public void putObject(Object key, Object value) {
cache.putObject(key, serialize((Serializable) value));
}
@Override
public Object getObject(Object key) {
Object value = cache.getObject(key);
Serializable result = deserialize((byte[]) value);
return result;
}
@Override
public Object removeObject(Object key) {
return null;
}
/**
* 对象序列化方法
* @param value
* @return
*/
private Object serialize(Serializable value) {
//序列化方法利用ObjectOutputStream
//声明字节数组输出流
ByteArrayOutputStream bos = null;
//声明对象输出流
ObjectOutputStream oos=null;
try {
//1.构建ObjectOutStream对象和字节数组对象
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
//2.将对象序列化到数组
oos.writeObject(value);
//刷新对象输出流
oos.flush();
//关闭对象输出流
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
return bos.toByteArray();
}
/**
* 对象反序列化方法
* @param value
* @return
*/
private Serializable deserialize(byte[] value) {
//声明对象输入流
ObjectInputStream ois=null;
//声明字节数组输入流
ByteArrayInputStream bis = null;
Serializable result=null;
try {
//构建字节数组输入流
bis = new ByteArrayInputStream(value);
//构建对象数组输入流
ois=new ObjectInputStream(bis);
result = (Serializable) ois.readObject();
ois.close();
} catch (IOException | ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
}
注意:序列化的时候,数据对象一定要实现序列化接口。
这是我的公众号YuFeng7604,欢迎交流!