John Jiang

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

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

          在一個并發(fā)任務(wù)被啟動之后,不要期望它總是會執(zhí)行完成。由于時間限制,資源限制,用戶操作,甚至是任務(wù)中的異常(尤其是運(yùn)行時異常),...都可能造成任務(wù)不能執(zhí)行完成。如何恰當(dāng)?shù)赝顺鋈蝿?wù)是一個很常見的問題,而且實(shí)現(xiàn)方法也不一而足。

          1. 任務(wù)
          創(chuàng)建一個并發(fā)任務(wù),遞歸地獲取指定目錄下的所有子目錄與文件的絕對路徑,最后再將這些路徑信息保存到一個文件中,如代碼清單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. 停止線程
          有一個很直接,也很干脆的方式來停止線程,就是調(diào)用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()在很久很久之前就不推薦使用了。根據(jù)官方文檔的介紹,該方法存在著固有的不安全性。當(dāng)停止線程時,將會釋放該線程所占有的全部監(jiān)視鎖,這就會造成受這些鎖保護(hù)的對象的不一致性。在執(zhí)行清單2的應(yīng)用程序時,它的運(yùn)行結(jié)果是不確定的。它可能會輸出一個文件,其中包含部分的被掃描過的目錄和文件。但它也很有可能什么也不輸出,因?yàn)樵趫?zhí)行FileWriter.write()的過程中,可能由于線程停止而造成了I/O異常,使得最終無法得到輸出文件。

          3. 可取消的任務(wù)
          另外一種十分常見的途徑是,在設(shè)計(jì)之初,我們就使任務(wù)是可被取消的。一般地,就是提供一個取消標(biāo)志或設(shè)定一個取消條件,一旦任務(wù)遇到該標(biāo)志或滿足了取消條件,就會結(jié)束任務(wù)的執(zhí)行。如代碼清單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實(shí)現(xiàn)提供一個cancel標(biāo)志,travleFiles()會遍歷新的文件之前檢測該標(biāo)志,若該標(biāo)志為true,則會立即返回。代碼清單4是使用新任務(wù)的應(yīng)用程序。
          清單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();
          }
          但有些時候使用可取消的任務(wù),并不能快速地退出任務(wù)。因?yàn)槿蝿?wù)在檢測取消標(biāo)志之前,可能正處于等待狀態(tài),甚至可能被阻塞著。對清單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;
              }
          }
          再執(zhí)行清單3中的應(yīng)用程序時,可能發(fā)現(xiàn)任務(wù)并沒有很快速的退出,而是又等待了大約7秒鐘才退出。如果在檢查cancel標(biāo)志之前要先獲取某個受鎖保護(hù)的資源,那么該任務(wù)就會被阻塞,并且無法確定何時能夠退出。對于這種情況,就需要使用中斷了。

          4. 中斷
          中斷是一種協(xié)作機(jī)制,它并不會真正地停止一個線程,而只是提醒線程需要被中斷,并將線程的中斷狀態(tài)設(shè)置為true。如果線程正在執(zhí)行一些可拋出InterruptedException的方法,如Thread.sleep(),Thread.join()和Object.wait(),那么當(dāng)線程被中斷時,上述方法就會拋出InterruptedException,并且中斷狀態(tài)會被重新設(shè)置為false。任務(wù)程序只要恰當(dāng)處理該異常,就可以正常地退出任務(wù)。對清單5再稍作修改,即,如果任務(wù)在睡眠時遇上了InterruptedException,那么就取消任務(wù)。如代碼清單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中的應(yīng)用程序,此時將調(diào)用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();
          }
          或者更進(jìn)一步,僅使用中斷狀態(tài)來控制程序的退出,而不再使用可取消的任務(wù)(即,刪除cancel標(biāo)志),將清單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;
                  }

                  
              }

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

              

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

                  
              }

              
          }

          5 小結(jié)
          本文介紹了三種簡單的退出并發(fā)任務(wù)的方法:停止線程;使用可取消任務(wù);使用中斷。毫無疑問,停止線程是不可取的。使用可取消的任務(wù)時,要避免任務(wù)由于被阻塞而無法及時,甚至永遠(yuǎn)無法被取消。一般地,恰當(dāng)?shù)厥褂弥袛嗍侨∠蝿?wù)的首選方式。
          posted on 2013-09-21 19:11 John Jiang 閱讀(2049) 評論(0)  編輯  收藏 所屬分類: JavaSEJavaConcurrency原創(chuàng)Java并發(fā)基礎(chǔ)實(shí)踐
          主站蜘蛛池模板: 清镇市| 常德市| 林口县| 社旗县| 凤台县| 尉犁县| 怀远县| 宁阳县| 遂宁市| 井研县| 榕江县| 丰都县| 鄂温| 漳平市| 彰武县| 上犹县| 隆尧县| 榆社县| 玉龙| 淳化县| 长岛县| 安平县| 昌宁县| 日土县| 建瓯市| 都匀市| 永春县| 墨脱县| 阜阳市| 奉节县| 景谷| 化州市| 昌都县| 溧水县| 剑阁县| 香格里拉县| 竹北市| 诸城市| 沙洋县| 青岛市| 沛县|