您的位置:首页 > 产品设计 > UI/UE

软件构造-经验-重绘,GUI与多线程的一次debug

2019-06-01 21:45 1781 查看

记一次debug

在哈工大软件构造的lab6中,要求我们用多线程来对猴子过河的决策过程进行仿真。

这个实验的构造思路其实比较简单,就是为每个猴子创建一个决策线程,每一只猴子都有自己的决策方式,但是所有猴子共用一条河。这也就意味着race condition的存在。

可能出现这样的情况,两只猴子同时决策选择同一个位置,结果会撞在一起,轻则位置重叠,重则程序崩溃。

为了避免这种情况,就需要我们小心翼翼地规划,保证线程安全的同时又要尽量提升效率。

学会多线程安全编程就是这次实验的主要目的。

 

实验中只要求实现GUI,没有要求实现动画效果,但是由于我比较闲,就是想试试看,于是我决定尝试动画化猴子的过河过程。

初步实现动画效果

我的设想是采用JComponent来进行猴子过河的绘制。

实验指导书上的要求说了,所有的猴子都是以1s为单位进行决策,这也就是说,所有猴子每1s都会更新一次状态,即使猴子并没有动。

如果要实现完全的动画效果,也就是把每一只猴子的移动过程都展现出来的话,假如有30只猴子,就意味着1s要进行30次重绘,猴子数量一多,势必影响到正常的决策计算的判断,会使猴子的运动仿真不准确,因此这是不可取的。

所以我觉得每秒种,在计时器驱动所有猴子进行决策之前,调用一次重绘,这样就能将资源消耗最小化。

基本的编程思路是这样的。

维护ladder上猴子位置的数据结构是一个Map<Integer, Integer> 的List,map的key值是猴子在ladder上的坐标,value就是猴子的id。list的不同元素代表不同的ladder

每秒钟的开始,用一个VisualMonkey类对该list进行读取,然后将其转化为坐标值,再传给Component,用以在Component上绘制。

这样设计的初衷是为了以后可以容易扩展一些,尽管把这个实验交上去之后我肯定不会想回来再看这个代码一眼。

基本思路很简单。

写好了程序之后尝试运行一下

。。。。。。咦?为什么重叠了。

出现了很严重的问题,同一只猴子同时出现在了ladder上的多个位置,一定是哪里出错了

排查bug

componnet的问题?

我看到这个bug的第一反应是——component重绘失败了,只绘制了新的图案,没有擦出旧的图案。

后面的事实证明这个猜测不无道理,但是我当时信誓旦旦错误一定出在这里,的确耽误了不少时间。

 

我在网上搜索了很多关于java的组件重绘的知识,包括repaint,validate,revalidate,update都试过了,从component到外面包含的Panel,遍历调用一次所有与重绘有关的方法,都解决不了这个问题。

接着我尝试了在Component的paintComponent方法里面对g进行清空,然后重新绘制,失败

接着我尝试重新创建Component对象,一切从头开始,但依旧失败了,

 

按照正常的逻辑,component部分应该没有问题,bug出在别的地方了,但是由于Lab3种与swing有着极大的怨气,所以我无论如何都想把它打倒,就这样耗费了一个小时,代码已经魔改到我也看不懂了,我终于决定探索别的道路。

 

被遗忘的update

我开始怀疑,也许component的重绘被忠实的执行了,有一种可能就是,旧的猴子位置没有被删除。

由于对猴子的ADT进行测试的时候,它们完美地完成了使命,所以我仍然觉得这里不会出问题。

等等,我好像忘记了什么

 

???

 

ADT没有错,component也没有错,那么问题出在......?

......

 

 啊,原来如此。

通过断点找到了VisualMonkey类,发现果然在从List到坐标值的换算种出了差错,我只对map进行了put,却没有把以前的clear

这不是线程安全带来的,但是多线程的复杂性的确会让人容易忽视这些问题。

 

看来需要在每次转换之前,把map清空

 

image的特殊性

让我们跑一次猴子看看

。。。。。。??

怎么还是有重叠的猴子?

 

仔细观察,发现前面的猴子都没有名字,只有一个猴子的图像,我大概知道了问题所在。

为了追求动画效果,我是使用emoji图片来表示猴子的。由于Image对象不属于Graphic对象

说的就是这个参数g

在paintComponent里面,我都是直接调用add方法来添加图片的

 而那些线条之类的元素是添加到Graphics对象里的:

调用repaint的时候,Graphics对象里的线条都被清空了,但是图片并没有被删除。

 

为了解决这个问题,我设置了一个set,每次把图片加到component上面的时候,同时把它们添加进这个set里

然后在每次repaint的时候,都要将component上的所有图片清空一次

现在应该没有问题了吧?

 

跑一下猴子看看:

终于解决了。

感想

在debug的过程中,应当冷静分析

有时候要认真思考问题出现的地方,比如说我执迷于打倒component,就忽视了别的地方。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: