John Jiang

          a cup of Java, cheers!
          https://github.com/johnshajiang/blog

             :: 首頁 ::  :: 聯系 :: 聚合  :: 管理 ::
            131 隨筆 :: 1 文章 :: 530 評論 :: 0 Trackbacks
          Java并發基礎實踐--退出任務I
          計劃寫一個"Java并發基礎實踐"系列,算作本人對Java并發學習與實踐的簡單總結。本文是該系列的第一篇,介紹了退出并發任務的最簡單方法。(2013.09.25最后更新)

          在一個并發任務被啟動之后,不要期望它總是會執行完成。由于時間限制,資源限制,用戶操作,甚至是任務中的異常(尤其是運行時異常),...都可能造成任務不能執行完成。如何恰當地退出任務是一個很常見的問題,而且實現方法也不一而足。

          1. 任務
          創建一個并發任務,遞歸地獲取指定目錄下的所有子目錄與文件的絕對路徑,最后再將這些路徑信息保存到一個文件中,如代碼清單1所示:
          清單1
          public class FileScanner implements Runnable {

              
          private File root = null;

              
          private List<String> filePaths = new ArrayList<String>();

              
          public FileScanner1(File root) {
                  
          if (root == null || !root.exists() || !root.isDirectory()) {
                      
          throw new IllegalArgumentException("root must be legal directory");
                  }

                  
          this.root = root;
              }

              @Override
              
          public void run() {
                  travleFiles(root);
                  
          try {
                      saveFilePaths();
                  } 
          catch (Exception e) {
                      e.printStackTrace();
                  }
              }

              
          private void travleFiles(File parent) {
                  String filePath 
          = parent.getAbsolutePath();
                  filePaths.add(filePath);

                  
          if (parent.isDirectory()) {
                      File[] children 
          = parent.listFiles();
                      
          for (File child : children) {
                          travleFiles(child);
                      }
                  }
              }

              
          private void saveFilePaths() throws IOException {
                  FileWriter fos 
          = new FileWriter(new File(root.getAbsoluteFile()
                          
          + File.separator + "filePaths.out"));
                  
          for (String filePath : filePaths) {
                      fos.write(filePath 
          + "\n");
                  }
                  fos.close();
              }
          }

          2. 停止線程
          有一個很直接,也很干脆的方式來停止線程,就是調用Thread.stop()方法,如代碼清單2所示:
          清單2
          public static void main(String[] args) throws Exception {
              FileScanner task 
          = new FileScanner(new File("C:"));
              Thread taskThread 
          = new Thread(task);
              taskThread.start();

              TimeUnit.SECONDS.sleep(
          1);
              taskThread.stop();
          }
          但是,地球人都知道Thread.stop()在很久很久之前就不推薦使用了。根據官方文檔的介紹,該方法存在著固有的不安全性。當停止線程時,將會釋放該線程所占有的全部監視鎖,這就會造成受這些鎖保護的對象的不一致性。在執行清單2的應用程序時,它的運行結果是不確定的。它可能會輸出一個文件,其中包含部分的被掃描過的目錄和文件。但它也很有可能什么也不輸出,因為在執行FileWriter.write()的過程中,可能由于線程停止而造成了I/O異常,使得最終無法得到輸出文件。

          3. 可取消的任務
          另外一種十分常見的途徑是,在設計之初,我們就使任務是可被取消的。一般地,就是提供一個取消標志或設定一個取消條件,一旦任務遇到該標志或滿足了取消條件,就會結束任務的執行。如代碼清單3所示:
          清單3
          public class FileScanner implements Runnable {

              
          private File root = null;

              
          private List<String> filePaths = new ArrayList<String>();

              
          private boolean cancel = false;

              
          public FileScanner(File root) {
                  
              }

              @Override
              
          public void run() {
                  
              }

              
          private void travleFiles(File parent) {
                  
          if (cancel) {
                      
          return;
                  }

                  String filePath 
          = parent.getAbsolutePath();
                  filePaths.add(filePath);

                  
          if (parent.isDirectory()) {
                      File[] children 
          = parent.listFiles();
                      
          for (File child : children) {
                          travleFiles(child);
                      }
                  }
              }

              
          private void saveFilePaths() throws IOException {
                  
              }

              
          public void cancel() {
                  cancel 
          = true;
              }
          }
          新的FileScanner實現提供一個cancel標志,travleFiles()會遍歷新的文件之前檢測該標志,若該標志為true,則會立即返回。代碼清單4是使用新任務的應用程序。
          清單4
          public static void main(String[] args) throws Exception {
              FileScanner task 
          = new FileScanner(new File("C:"));
              Thread taskThread 
          = new Thread(task);
              taskThread.start();

              TimeUnit.SECONDS.sleep(
          3);
              task.cancel();
          }
          但有些時候使用可取消的任務,并不能快速地退出任務。因為任務在檢測取消標志之前,可能正處于等待狀態,甚至可能被阻塞著。對清單2中的FileScanner稍作修改,讓每次訪問新的文件之前先睡眠10秒鐘,如代碼清單5所示:
          清單5
          public class FileScanner implements Runnable {

              

              
          private void travleFiles(File parent) {
                  
          try {
                      TimeUnit.SECONDS.sleep(
          10);
                  } 
          catch (InterruptedException e) {
                      e.printStackTrace();
                  }

                  
          if (cancel) {
                      
          return;
                  }

                  
              }

              
          private void saveFilePaths() throws IOException {
                  
              }

              
          public void cancel() {
                  cancel 
          = true;
              }
          }
          再執行清單3中的應用程序時,可能發現任務并沒有很快速的退出,而是又等待了大約7秒鐘才退出。如果在檢查cancel標志之前要先獲取某個受鎖保護的資源,那么該任務就會被阻塞,并且無法確定何時能夠退出。對于這種情況,就需要使用中斷了。

          4. 中斷
          中斷是一種協作機制,它并不會真正地停止一個線程,而只是提醒線程需要被中斷,并將線程的中斷狀態設置為true。如果線程正在執行一些可拋出InterruptedException的方法,如Thread.sleep(),Thread.join()和Object.wait(),那么當線程被中斷時,上述方法就會拋出InterruptedException,并且中斷狀態會被重新設置為false。任務程序只要恰當處理該異常,就可以正常地退出任務。對清單5再稍作修改,即,如果任務在睡眠時遇上了InterruptedException,那么就取消任務。如代碼清單6所示:
          清單6
          public class FileScanner implements Runnable {

              

              
          private void travleFiles(File parent) {
                  
          try {
                      TimeUnit.SECONDS.sleep(
          10);
                  } 
          catch (InterruptedException e) {
                      cancel();
                  }

                  
          if (cancel) {
                      
          return;
                  }

                  
              }

              
          }
          同時將清單4中的應用程序,此時將調用Thread.interrupt()方法去中斷線程,如代碼清單7所示:
          清單7
          public static void main(String[] args) throws Exception {
              FileScanner3 task 
          = new FileScanner3(new File("C:"));
              Thread taskThread 
          = new Thread(task);
              taskThread.start();

              TimeUnit.SECONDS.sleep(
          3);
              taskThread.interrupt();
          }
          或者更進一步,僅使用中斷狀態來控制程序的退出,而不再使用可取消的任務(即,刪除cancel標志),將清單6中的FileScanner修改成如下:
          清單8
          public class FileScanner implements Runnable {

              

              
          private void travleFiles(File parent) {
                  
          try {
                      TimeUnit.SECONDS.sleep(
          10);
                  } 
          catch (InterruptedException e) {
                      Thread.currentThread().interrupt();
                  }

                  
          if (Thread.currentThread().isInterrupted()) {
                      
          return;
                  }

                  
              }

              
          }
          再次執行清單7的應用程序后,新的FileScanner也能即時的退出了。值得注意的是,因為當sleep()方法拋出InterruptedException時,該線程的中斷狀態將又會被設置為false,所以必須要再次調用interrupt()方法來保存中斷狀態,這樣在后面才可以利用中斷狀態來判定是否需要返回travleFiles()方法。當然,對于此處的例子,在收到InterruptedException時也可以選擇直接返回,如代碼清單9所示:
          清單9
          public class FileScanner implements Runnable {

              

              
          private void travleFiles(File parent) {
                  
          try {
                      TimeUnit.SECONDS.sleep(
          10);
                  } 
          catch (InterruptedException e) {
                      
          return;
                  }

                  
              }

              
          }

          5 小結
          本文介紹了三種簡單的退出并發任務的方法:停止線程;使用可取消任務;使用中斷。毫無疑問,停止線程是不可取的。使用可取消的任務時,要避免任務由于被阻塞而無法及時,甚至永遠無法被取消。一般地,恰當地使用中斷是取消任務的首選方式。
          posted on 2013-09-21 19:11 John Jiang 閱讀(2049) 評論(0)  編輯  收藏 所屬分類: JavaSEJavaConcurrency原創Java并發基礎實踐
          主站蜘蛛池模板: 韶关市| 金坛市| 隆子县| 十堰市| 海伦市| 永年县| 驻马店市| 闽侯县| 芷江| 普陀区| 梓潼县| 会宁县| 丹东市| 准格尔旗| 鹰潭市| 安乡县| 会泽县| 福泉市| 宜良县| 东阿县| 沂源县| 灵宝市| 宜章县| 武平县| 阿合奇县| 曲麻莱县| 屏山县| 松原市| 忻城县| 利辛县| 安塞县| 平顶山市| 孝义市| 清丰县| 皮山县| 奉化市| 阿勒泰市| 左权县| 新绛县| 克山县| 池州市|