背景:有多个客户端会同时上传压缩包至平台,压缩包中有多个txt文件,平台会逐一解析然后将他们的数据入库。
但是,在实践中发现,经常会有数据没有写入数据库的现象发生。但是log记录的是平台对其有进行分析的。
那么问题的原因就可以缩小到“数据解析”->"数据入库”的环节。
我的代码中, 数据解析的类是StabilityPerfReportHandler.java,数据入库的类是StabilityPerfReportDao.java
先看一下整体的代码结构
因为有多个文件,如果等到文件解析完再返回客户端一个值,则容易引起返回超时,导致文件重传。所以在Handler中是采用多线程对每个文件进行数据解析及入库的。那么就猜测,数据有解析但是没有写入数据库的原因是多线程同时调用了StabilityPerfReportDao.java中的入库函数,导致有冲突。
那么我的第一个尝试是:给每个写入数据库的函数加锁
原先:
public void insertCpuData(List listCpuResults)
改后:
public synchronized void insertCpuData(List listCpuResults)
事实证明,这种修改是没有作用的。
但是可以肯定的一点就是在写入数据库的时候是存在冲突问题的,导致数据写入的时候,有些数据没写入丢失。
再次梳理了一下代码,发现在解析完数据要写入数据库时采用的是单例的形式
StabilityPerfReportDao.getInstance().insertCpuData(stabilityPerfCPUPos);
查看了StabilityPerfReportDao的单例代码,发现是如下的:
public static StabilityPerfReportDao getInstance() {
if (null == stabilityPerfReportDao) {
synchronized (StabilityPerfReportDao.class) {
if (null == stabilityPerfReportDao) {
stabilityPerfReportDao = new StabilityPerfReportDao();
}
}
}
return instance;
}
查阅了相关资料,发现这段代码在多线程并发的情况下是有问题的。
最根本的原因是,java寄存器的读写是无序的。
当我们new一个对象的时候,java是经历以下三个步骤的:
a.给实例分配内存
b.初始化构造器(构造器的作用就是给变量赋值)
c.将引用指向分配的对存
我们的期望是a->b->c,但是由于java寄存器的读写是无序的,所以他可能的顺序是a->c->b。如果顺序是a->c->b,那么我们获得的对象是没有被初始化的,这样就会产生问题。
结合到我们上述有问题的代码来看,就是以下这样的
a.线程A、B同时调用StabilityPerfReportDao.getInstance()
b.线程B先进入,判断到StabilityPerfReportDao为空,则进入到synchronized模块中,new一个StabilityPerfReportDao对象
c.此时线程A进入,判断到StabilityPerfReportDao不为空,便返回了这个instance,但是这个instance是还没有经过构造器初始化的,所以线程A拿到这个instance去执行接下来的操作会出现问题。
现在问题找到了,就要相处解决的方法了,以下是我解决的代码
private static StabilityPerfReportDao instance = new StabilityPerfReportDao();
public static StabilityPerfReportDao getInstance() {
return instance;
}
在类加载的时候,就去定义一个静态变量instance并初始化它。接下来每个外部类调用getInstance去获得唯一备份的instance。代码这么修改之后,就完美地解决了问题了。
网友评论