# 《并发设计模式》第46章-串行线程封闭模式-导出报表数据错乱了

作者:冰河
星球:http://m6z.cn/6aeFbs (opens new window)
博客:https://binghe.gitcode.host (opens new window)
文章汇总:https://binghe.gitcode.host/md/all/all.html (opens new window)
源码获取地址:https://t.zsxq.com/0dhvFs5oR (opens new window)

沉淀,成长,突破,帮助他人,成就自我。

  • 本章难度:★★☆☆☆
  • 本章重点:了解串行线程封闭模式的使用场景,能够初步结合自身项目实际场景思考如何将串行线程封闭模式灵活应用到自身实际项目中。

大家好,我是冰河~~

看似一个很简单的问题,比如导出报表,下载文件等,看似很简单的问题,稍不注意就可能会出现致命的问题,尤其是导出系统的分析结果数据。很多互联网公司都有报表系统,这些报表系统主要是对其他业务系统的结果进行分析统计,为运营的决策提供数据参考。如果报表系统导出的统计数据错乱,那势必会影响运营人员后续的运营策略和方向。

# 一、故事背景

这天下班时间到了,小菜想下个早班,很久没有这么早下班了,今天想回去跟女朋友一起出去逛逛,正在小菜收拾东西时,突然身后传来一阵急促的说话声:“小菜,快点帮忙看看是不是代码有问题,运营那边在报表系统里导出来的统计数据都错乱了,这个问题很急,要尽快处理下”。小菜回过头,“妈的,果然是你”,在心里嘀咕着,“早不来,晚不来,非要这个时候来”,此时的小菜心里已经一万个“草泥马”在奔腾。但是,不能表现出来,于是说了句:“我看看”。

于是小菜便重新打开电脑,把报表系统的代码拉取下来,开始看报表系统的代码。虽然这报表系统的代码栏吧,但是也不至于数据错乱呀?这下又要耽误时间排查问题了。于是小菜拿出手机给女朋友打了个电话:“喂,今天我就不陪你出去逛了,公司还有点问题需要处理,晚点我再下班”。。。

# 二、求助老王

报表系统一般是公司对其他业务系统数据的统计分析,形成报表后进行展示,也会提供一些导出文件的功能。一个简单的文件导出功能能出现什么问题呢?怎么就会出现数据错乱呢?小菜一边调试代码一边在心里嘀咕着,可看了半天也没发现什么问题,心情也是越来越急躁。此时小菜发现老王还没下班,于是走到老王的工位旁寻求帮助。

老王老了会儿代码说:“这问题很简单,我给你讲讲吧”。

“好的”。

于是老王就吧啦吧啦的说处起来。。。

# 三、重现问题

为了更好的重现问题,我们以代码的形式给出案例,在案例代码中,会模拟实现一个文件下载客户端,并且会在多个线程中调用文件客户端下载文件。具体实现步骤如下所示。

(1)实现FileClient类

FileClient类主要是模拟实现文件客户端,提供了初始化客户端的initClient()方法,以及下载文件的方法downloadFile()方法。

源码详见:io.binghe.concurrent.design.thread.close.common.FileClient。

public class FileClient {

    public void initClient(){
        System.out.println(Thread.currentThread().getName() + "初始化文件客户端开始");
        long startTime = System.currentTimeMillis();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "初始化文件客户端完毕,耗时:" + (System.currentTimeMillis() - startTime) + "ms");
    }

    public void downloadFile(String fileName){
        System.out.println(Thread.currentThread().getName() + "下载文件开始");
        long startTime = System.currentTimeMillis();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "下载文件完毕,下载的文件为:" + fileName + ", 耗时:" + (System.currentTimeMillis() - startTime) + "ms");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

(2)实现FileService接口

FileService接口是文件相关的服务接口,主要提供了一个下载文件的downloadFile()方法。

源码详见:io.binghe.concurrent.design.thread.close.wrong.FileService。

public interface FileService {
    void downloadFile(String fileName);
}
1
2
3

(3)实现FileServiceImpl类

FileServiceImpl类是文件相关的服务实现类,主要实现了FileService接口,并实现了downloadFile()方法。

源码详见:io.binghe.concurrent.design.thread.close.wrong.FileServiceImpl。

public class FileServiceImpl implements FileService{

    private FileClient fileClient;

    public FileServiceImpl(){
        this.fileClient = new FileClient();
        fileClient.initClient();
    }

    @Override
    public void downloadFile(String fileName) {
        fileClient.downloadFile(fileName);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

(4)实现FileWrongTest类

FileWrongTest类是案例的模拟类,也是案例代码的测试类,通过FileWrongTest类可以运行程序案例。

源码详见:io.binghe.concurrent.design.thread.close.wrong.FileWrongTest。

# 查看全文

加入冰河技术 (opens new window)知识星球,解锁完整技术文章与完整代码