1. 程式人生 > >Java多線程編程作業總結

Java多線程編程作業總結

每次 ont 指向 class 構造方法 調度器 eva 大量 問題

一.多線程知識總結

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。

類圖關系

技術分享圖片

  1. 線程類: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(),實現線程同步。
  2. 關於電梯的類,我將電梯類單獨作為一個類,只包括電梯的屬性和一個方法,然後將電梯的運行單獨作為一個類,Elevator_move類,改類包含所有電梯的方法,以及時間的計算。在Elevator_run線程中通過構造方法傳入Elevator類,並新構建一個Elevator_move類的對象。
  3. 關於調度類,調度的方法並不在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的使用增長主要時輸入的時候需要創建線程的原因。

類圖說明

技術分享圖片

這次的類圖明顯比較亂

  1. 關於線程類,主要有Modified,Path_changed,Renamed,Size_changed,Test_thread五個線程類。前四個線程不會開始就啟動,只有輸入有效監控對象,以及監控過程才會新建相應的監控線程。然後是Test_thread是用於測試的線程。
  2. 關於監控文件的類,監控文件的時候,如果是監控目錄,那麽目錄下每一個文件改變都會觸發監控器。而目錄下文件改變後還要接著監控,我實現的方式是,首先構建一個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 ;
        }
    }

  3. 我們將File_all的一個ArrayList傳入方法test中,然後就可以調用該方法,獲得fileList、也就是存儲了所有文件屬性的動態數組。
  4. 那麽如果兩個線程都監控同一文件會不會有資源競爭的問題呢?答案是會的,因為假設線程1是監控A路徑下的文件,監控的是Renamed Then recover。線程2監控的也是A路徑下的文件,監控是Modified Then details,那麽入股哦線程1先運行,那麽在線程1還沒有recover A路徑下的文件的時候,線程2將會得到錯誤的結果,發現沒有文件。我我采用的解決的方法是通過鎖住文件的方法,但是只會鎖住SIze_changed,和Renamed兩個線程中文件的改變,這樣監控同一文件的時候,如果有Renamed的情況就會在操作執行完之後才會運行接下來的線程。
  5. 快照的實現,沒有仔細閱讀指導書,完全按自己想法來,沒有快照的概念,只知道需要比較前後兩次文件的差異,比較的方法,偽代碼如下:
    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的運行大大增加。中間會有一些較小的峰值,是出租車的運行。

類圖分析

技術分享圖片

  1. 線程類:其實只有Scheduler和Taxi_move,而其中的Input是之前寫的,程序並沒有用到,忘記改了,也沒有刪除,然後就被報了一個設計缺陷,…………,這是提交時候的類圖,所以Input還在Taxi_move線程是表示出租車運行的線程,100輛出租車也就有100個Taxi_move線程,Taxi_move線程主要是出租車的運行。而Scheduler線程是調度器線程,該線程的創建時在Request類中,每一個有效輸入就創建一個調度線程,在3s之內,如果有車接應,那麽就完成一次調度,然後通過改變出租車的屬性,Taxi_move線程就會讓出租車按照一定的方式運行起來。所以出租車是Taxi_move線程和Scheduler線程的競爭資源?也不完全是,因為,調度器不會調度運行狀態的車,而Taxi_move只會調度運行狀態的車。
  2. 調用GUI,關於怎樣將出租車顯示在GUI上呢?當然是每次出租車狀態改變的時候就刷新出租車的位置啦。
  3. 關於最短路徑,與出租車在遊蕩的時候的隨機路徑,都在出租車類裏面。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;
                }
            }

    由於是連通圖,所以總會有路走的。

時序圖分析

技術分享圖片

可以看出各個類之間的關系

度量分析

技術分享圖片

第一次圈復雜度過關了,是不是很高興,但是這其實是假的。經過多次測試,我發現問題,真實情況是這樣的:

技術分享圖片

然而原因是,我太著急了,第一張圖是還沒有完全測試完的情況還沒有完全顯示出圈復雜度。

三.第二次作業總結

關於多線程編程的幾點思考

  1. 線程同步:不知道我這種想法對不對,但是從多線程電梯開始,我就是這麽想的,一切的資源競爭關系都是生產者與消費者之間的關系。當然也會有同一個類同時扮演兩種角色,這時分析起來會比較復雜。
  2. 線程的運行結束,在多線程電梯那裏,我就沒有處理好電梯的線程的結束問題,因為我們很多情況下用的是while(true)來運行線程,就容易導致這一點,所以必要的標記還是十分重要的。

心得體會

  老實說,我其實每次作業都是抱著,只要有效就行的想法寫的,所以最後寫的也不是特別好,這三周真的是十分的艱難啊。第一周的時候想到一句話:“No Pains ,No Gains” 。再咬咬牙吧,第二周的時候又想到一句話“如果有一天,你覺得生活如此的艱難,那也許是這次的收獲將十分的巨大”。第二周的時候,第三次作業都快想放棄了,生命要緊啊,後來還是咬咬牙堅持了下來,因為又想到一句話”將來的你,一定會感謝現在如此努力的自己“。在這十分艱難的日子裏,我也只能靠這些雞湯度日了。所以想和大家共勉吧,來 ”幹了這碗雞湯“。

Java多線程編程作業總結