軟體構造-經驗-重繪,GUI與多執行緒的一次debug
記一次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,就忽視了別的地