Java多線程編程作業總結
一.多線程知識總結
1.線程同步
有關創建線程的知識就不過多的敘述了。就從主要的開始講吧,講一下線程的同步。與操作系統中的進程同步一樣,線程同樣面臨著資源共享的問題,怎樣處理線程的資源共享是運用多線程最重要的地方。在Java中是引入鎖這一概念來處理多線程之間的資源競爭的關系的。“鎖”的對象可以是代碼塊,方法,還可以是對象。一旦某一部分被鎖住,我們稱該部分獲取了鎖。那麽在java多個線程中,只有擁有鎖的線程才可以運行,其他線程不能運行。如果我們將競爭資源的代碼塊鎖起來,就可以避免發生沖突。在運用鎖的過程中,通常使用的是synchronized();表示鎖住某一部分代碼塊。那麽我們還介紹以下,wait(),和notifyAll(), notify的用法。舉個例子,以多線程電梯為例子吧,在多線程電梯中的輸入與1調度器之間的關系。我們一次輸入同一時刻的多條指令,我們將這多條指令稱為臨時隊列。每次調度每一次輸入的指令,將每一次輸入的指令加入到總的隊列中去。那麽就要求輸入的時候,不可以運行調度器訪問輸入的臨時隊列,因為如果多次訪問將造成一次輸入被調度多次,而調度的時候也不能寫入指令,因為多次寫入會導致重寫前一次的輸入,也就忽略了前一次的輸入。看一下代碼:
while(true) { while(this.sign!=0) { //表示隊列不為空 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } /** 表示中間處理過程 */ sign=1; //用於標記 notifyAll(); }
二.三次作業分析
2.多線程電梯
剛剛接觸多線程確實是十分的艱難,剛拿到的時候毫無頭緒。最後的成績也不是很好,不過之後debug之後感覺就好多了。首先看一下JProfiler性能分析的結果。
JProfiler性能分析的結果
用wait()方法鎖住的線程表示為紅色,在輸入階段CPU的使用也是最多的,然後是三個電梯都接收指令後開始調度。最後輸入END後輸入線程結束,還有電梯沒有執行完。直到執行完後結束。分為三個階段的化輸入的時候也會有調度,所以處理器的使用率最高,然後是三部電梯同時調度,最後是一部電梯調度,可以看出三部電梯同時調度的時候CPU的使用要比單個電梯調度的時候高很多。說明除了運行線程,CPU處理線程之間的調度也占用CPU。
類圖關系
- 線程類:Elevator_run; Input; Super_ride。其中Input表示輸入線程類,然後是Elevator_run表示電梯運行線程類,所以創建三個Elevator_run線程類,然後是Super_ride線程類,表示調度線程類。線程之間是並行的,但是輸入與調度之間的線程必須有wait()和notify()關系,防止對w_legal類中的數據沖突而產生錯誤。同時,Super_ride類和Elevator_run類之間共享資源Elevator類,所以在Super_ride調用Elevator時Elevator_run不能調用,同理,Elevator_run調用Elevator時Super_ride不能調用,所以在這兩個線程中使用synchronized(),實現線程同步。
- 關於電梯的類,我將電梯類單獨作為一個類,只包括電梯的屬性和一個方法,然後將電梯的運行單獨作為一個類,Elevator_move類,改類包含所有電梯的方法,以及時間的計算。在Elevator_run線程中通過構造方法傳入Elevator類,並新構建一個Elevator_move類的對象。
- 關於調度類,調度的方法並不在Super_ride中直接實現,同樣我也寫了一個專門負責調度的類,只包括調度的屬性以及方法。調度的時候首先在Super_ride中通過構造方法傳入電梯與指令,然後實例化一個調度器,將電梯與臨時隊列傳入調度器,就可以得到每個電梯的隊列。
時序圖分析
度量分析
3.文件系統
有關IFTTT
https://en.wikipedia.org/wiki/IFTTT,或者,https://baike.baidu.com/item/ifttt/8378533?fr=aladdin,而IFTTT本事也是一款十分強大的app,大家可以去試試,https://ifttt.com/products。
JProfiler性能分析的結果
由於對於這次的測試,測試文件不是很多,所以CPU的使用比較低。從圖中可以明顯的看出我存在的一個問題,那就是沒有一直監控文件。再之後我會說明。一個輸入會增加一個線程,也就是說,每一個文件對應一個監控線程,用來實時監控改文件。對於為什麽會有線程結束,是因為,再我的程序中,監控文件只會監控一次變化,但是監控目錄會監控多次變化。在類圖部分會說明原因。開始階段CPU的使用增長主要時輸入的時候需要創建線程的原因。
類圖說明
這次的類圖明顯比較亂
- 關於線程類,主要有Modified,Path_changed,Renamed,Size_changed,Test_thread五個線程類。前四個線程不會開始就啟動,只有輸入有效監控對象,以及監控過程才會新建相應的監控線程。然後是Test_thread是用於測試的線程。
- 關於監控文件的類,監控文件的時候,如果是監控目錄,那麽目錄下每一個文件改變都會觸發監控器。而目錄下文件改變後還要接著監控,我實現的方式是,首先構建一個File_all類;
class File_all {protected String name=null; protected long f_l=0; protected long l_t=0; protected String path=null; }
由於File方法返回的是指向文件的指針,那麽當路徑改變的時候,返回值就會改變,所以,File_all的作用是存入路徑改變之前的文件的屬性,用於之後的文件改變後用於比較。那麽是如何獲取整個目錄下所有的文件的呢?當然是使用遞歸搜索的方法。
class Test { protected void test(String fileDir, ArrayList<File_all> fileList) { // = new ArrayList<File_all>(); File file = new File(fileDir); File[] files = file.listFiles();// 獲取目錄下的所有文件或文件夾 if (files == null) {// 如果目錄為空,直接退出 return ; } // 遍歷,目錄下的所有文件,將文件屬性存起來 for (File f : files) { File_all tem_file = new File_all(); if (f.isFile()) { tem_file.file = f; tem_file.name = f.getName(); tem_file.f_l = f.length(); tem_file.l_t = f.lastModified(); fileList.add(tem_file); } else if (f.isDirectory()) { test(f.getAbsolutePath(),fileList); } } return ; } }
- 我們將File_all的一個ArrayList傳入方法test中,然後就可以調用該方法,獲得fileList、也就是存儲了所有文件屬性的動態數組。
- 那麽如果兩個線程都監控同一文件會不會有資源競爭的問題呢?答案是會的,因為假設線程1是監控A路徑下的文件,監控的是Renamed Then recover。線程2監控的也是A路徑下的文件,監控是Modified Then details,那麽入股哦線程1先運行,那麽在線程1還沒有recover A路徑下的文件的時候,線程2將會得到錯誤的結果,發現沒有文件。我我采用的解決的方法是通過鎖住文件的方法,但是只會鎖住SIze_changed,和Renamed兩個線程中文件的改變,這樣監控同一文件的時候,如果有Renamed的情況就會在操作執行完之後才會運行接下來的線程。
- 快照的實現,沒有仔細閱讀指導書,完全按自己想法來,沒有快照的概念,只知道需要比較前後兩次文件的差異,比較的方法,偽代碼如下:
if(file_2.isDirectory()) { chose = 0; ArrayList<File_all> FileList = new ArrayList<File_all>(); //新建FileList存儲舊的文件 test_sc.test(obj_path,FileList); //遞歸獲取舊的文件屬性,並存儲 while(true) { ArrayList<File_all> FileList2 = new ArrayList<File_all>(); //新建新文件 test_sc.test(obj_path,FileList2); //遞歸獲取新的文件屬性,並存儲 /** 各種操作 */ FileList.clear(); //各種比較操作結束後,也就是將新舊文件比較後,清空新舊文件 FileList2.clear(); test_sc.test(obj_path,FileList); //再一次獲得舊文件,存儲舊文件,用於之後比較 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
時序圖分析
度量分析
度量分析的結果顯示,代碼還是有漏洞。
4.出租車
JProfiler性能分析的結果
出租車的測試,我采用了一次性輸入了大量的請求。從CPU運行的幾個峰值來看,第一個是程序開始,創建100個出租車線程,可以看出創建線程是比較占用CPU的,然後是每一次輸入,每一次輸入是比較占用CPU的,原因是,每一個輸入或構建一個調度線程,同樣需要創建多個線程,就導致CPU的運行大大增加。中間會有一些較小的峰值,是出租車的運行。
類圖分析
- 線程類:其實只有Scheduler和Taxi_move,而其中的Input是之前寫的,程序並沒有用到,忘記改了,也沒有刪除,然後就被報了一個設計缺陷,…………,這是提交時候的類圖,所以Input還在Taxi_move線程是表示出租車運行的線程,100輛出租車也就有100個Taxi_move線程,Taxi_move線程主要是出租車的運行。而Scheduler線程是調度器線程,該線程的創建時在Request類中,每一個有效輸入就創建一個調度線程,在3s之內,如果有車接應,那麽就完成一次調度,然後通過改變出租車的屬性,Taxi_move線程就會讓出租車按照一定的方式運行起來。所以出租車是Taxi_move線程和Scheduler線程的競爭資源?也不完全是,因為,調度器不會調度運行狀態的車,而Taxi_move只會調度運行狀態的車。
- 調用GUI,關於怎樣將出租車顯示在GUI上呢?當然是每次出租車狀態改變的時候就刷新出租車的位置啦。
- 關於最短路徑,與出租車在遊蕩的時候的隨機路徑,都在出租車類裏面。GUI.java中提供了查找最短路徑的方法,也許有部分同學會使用guiInfo類中的distance方法,但是仔細以看,其中D[][],是guiInfo類的一個屬性,那麽我們就可以直接通過pointbfs(root)方法,獲取任意一點到root的最短路徑,然後將路徑存在D[][]中,這樣會大大的減少最短路徑的計算時間。采用真實時間的同學,可以這樣做,不過在下用的假時間就不必太在乎了。關於隨機路徑,
while(true) { direction = (int)(1+Math.random()*5); //隨機選一個位置 if(direction==1 && this.location_x<79 && map_mess.graph[location][location_1]==1) { this.location_x = this.location_x+1; break; } else if(direction==2 && this.location_x>0 && map_mess.graph[location][location_2]==1) { this.location_x = this.location_x-1; break; } else if(direction==3 && this.location_y>0 && map_mess.graph[location][location_3]==1) { this.location_y = this.location_y-1; break; } else if(direction==4 && this.location_y<79 && map_mess.graph[location][location_4]==1) { this.location_y = this.location_y+1; break; } }
由於是連通圖,所以總會有路走的。
時序圖分析
可以看出各個類之間的關系
度量分析
第一次圈復雜度過關了,是不是很高興,但是這其實是假的。經過多次測試,我發現問題,真實情況是這樣的:
然而原因是,我太著急了,第一張圖是還沒有完全測試完的情況還沒有完全顯示出圈復雜度。
三.第二次作業總結
關於多線程編程的幾點思考
- 線程同步:不知道我這種想法對不對,但是從多線程電梯開始,我就是這麽想的,一切的資源競爭關系都是生產者與消費者之間的關系。當然也會有同一個類同時扮演兩種角色,這時分析起來會比較復雜。
- 線程的運行結束,在多線程電梯那裏,我就沒有處理好電梯的線程的結束問題,因為我們很多情況下用的是while(true)來運行線程,就容易導致這一點,所以必要的標記還是十分重要的。
心得體會
老實說,我其實每次作業都是抱著,只要有效就行的想法寫的,所以最後寫的也不是特別好,這三周真的是十分的艱難啊。第一周的時候想到一句話:“No Pains ,No Gains” 。再咬咬牙吧,第二周的時候又想到一句話“如果有一天,你覺得生活如此的艱難,那也許是這次的收獲將十分的巨大”。第二周的時候,第三次作業都快想放棄了,生命要緊啊,後來還是咬咬牙堅持了下來,因為又想到一句話”將來的你,一定會感謝現在如此努力的自己“。在這十分艱難的日子裏,我也只能靠這些雞湯度日了。所以想和大家共勉吧,來 ”幹了這碗雞湯“。
Java多線程編程作業總結