在哈工大軟件構造的lab6中,要求咱們用多線程來對猴子過河的決策過程進行仿真。java
這個實驗的構造思路其實比較簡單,就是爲每一個猴子建立一個決策線程,每一隻猴子都有本身的決策方式,可是全部猴子共用一條河。這也就意味着race condition的存在。編程
可能出現這樣的狀況,兩隻猴子同時決策選擇同一個位置,結果會撞在一塊兒,輕則位置重疊,重則程序崩潰。安全
爲了不這種狀況,就須要咱們當心翼翼地規劃,保證線程安全的同時又要儘可能提高效率。數據結構
學會多線程安全編程就是此次實驗的主要目的。多線程
實驗中只要求實現GUI,沒有要求實現動畫效果,可是因爲我比較閒,就是想試試看,因而我決定嘗試動畫化猴子的過河過程。測試
個人設想是採用JComponent來進行猴子過河的繪製。動畫
實驗指導書上的要求說了,全部的猴子都是以1s爲單位進行決策,這也就是說,全部猴子每1s都會更新一次狀態,即便猴子並無動。線程
若是要實現徹底的動畫效果,也就是把每一隻猴子的移動過程都展示出來的話,假若有30只猴子,就意味着1s要進行30次重繪,猴子數量一多,勢必影響到正常的決策計算的判斷,會使猴子的運動仿真不許確,所以這是不可取的。debug
因此我以爲每秒種,在計時器驅動全部猴子進行決策以前,調用一次重繪,這樣就能將資源消耗最小化。設計
基本的編程思路是這樣的。
維護ladder上猴子位置的數據結構是一個Map<Integer, Integer> 的List,map的key值是猴子在ladder上的座標,value就是猴子的id。list的不一樣元素表明不一樣的ladder
每秒鐘的開始,用一個VisualMonkey類對該list進行讀取,而後將其轉化爲座標值,再傳給Component,用以在Component上繪製。
這樣設計的初衷是爲了之後能夠容易擴展一些,儘管把這個實驗交上去以後我確定不會想回來再看這個代碼一眼。
基本思路很簡單。
寫好了程序以後嘗試運行一下
。。。。。。咦?爲何重疊了。
出現了很嚴重的問題,同一只猴子同時出如今了ladder上的多個位置,必定是哪裏出錯了
我看到這個bug的第一反應是——component重繪失敗了,只繪製了新的圖案,沒有擦出舊的圖案。
後面的事實證實這個猜想不無道理,可是我當時信誓旦旦錯誤必定出在這裏,的確耽誤了很多時間。
我在網上搜索了不少關於java的組件重繪的知識,包括repaint,validate,revalidate,update都試過了,從component到外面包含的Panel,遍歷調用一次全部與重繪有關的方法,都解決不了這個問題。
接着我嘗試了在Component的paintComponent方法裏面對g進行清空,而後從新繪製,失敗
接着我嘗試從新建立Component對象,一切從頭開始,但依舊失敗了,
按照正常的邏輯,component部分應該沒有問題,bug出在別的地方了,可是因爲Lab3種與swing有着極大的怨氣,因此我不管如何都想把它打倒,就這樣耗費了一個小時,代碼已經魔改到我也看不懂了,我終於決定探索別的道路。
我開始懷疑,也許component的重繪被忠實的執行了,有一種可能就是,舊的猴子位置沒有被刪除。
因爲對猴子的ADT進行測試的時候,它們完美地完成了使命,因此我仍然以爲這裏不會出問題。
等等,我好像忘記了什麼
???
ADT沒有錯,component也沒有錯,那麼問題出在......?
......
啊,原來如此。
經過斷點找到了VisualMonkey類,發現果真在從List到座標值的換算種出了差錯,我只對map進行了put,卻沒有把之前的clear
這不是線程安全帶來的,可是多線程的複雜性的確會讓人容易忽視這些問題。
看來須要在每次轉換以前,把map清空
讓咱們跑一次猴子看看
。。。。。。??
怎麼仍是有重疊的猴子?
仔細觀察,發現前面的猴子都沒有名字,只有一個猴子的圖像,我大概知道了問題所在。
爲了追求動畫效果,我是使用emoji圖片來表示猴子的。因爲Image對象不屬於Graphic對象
說的就是這個參數g
在paintComponent裏面,我都是直接調用add方法來添加圖片的
而那些線條之類的元素是添加到Graphics對象裏的:
調用repaint的時候,Graphics對象裏的線條都被清空了,可是圖片並無被刪除。
爲了解決這個問題,我設置了一個set,每次把圖片加到component上面的時候,同時把它們添加進這個set裏
而後在每次repaint的時候,都要將component上的全部圖片清空一次
如今應該沒有問題了吧?
跑一下猴子看看:
終於解決了。
在debug的過程當中,應當冷靜分析
有時候要認真思考問題出現的地方,好比說我執迷於打倒component,就忽視了別的地方。