John Jiang

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

             :: 首頁 ::  :: 聯系 :: 聚合  :: 管理 ::
            131 隨筆 :: 1 文章 :: 530 評論 :: 0 Trackbacks
          <2013年10月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          留言簿(3)

          隨筆分類(415)

          隨筆檔案(130)

          文章分類

          Attach

          搜索

          積分與排名

          最新評論

          閱讀排行榜

          評論排行榜

          Java并發基礎實踐--退出任務II
          本系列上一篇中所述的退出并發任務的方式都是基于JDK 5之前的API,本文將介紹使用由JDK 5引入的并發工具包中的API來退出任務。(2013.10.08最后更新)

              在本系列的前一篇中講述了三種退出并發任務的方式--停止線程;可取消的任務;中斷,但都是基于JDK 5之前的API。本篇將介紹由JDK 5引入的java.concurrent包中的Future來取消任務的執行。

          1. Future模式
              Future是并發編程中的一種常見設計模式,它相當于是Proxy模式與Thread-Per-Message模式的結合。即,每次都創建一個單獨的線程去執行一個耗時的任務,并且創建一個Future對象去持有實際的任務對象,在將來需要的時候再去獲取實際任務的執行結果。
          依然先創建一個用于掃描文件的任務FileScannerTask,如代碼清單1所示,
          清單1
          public class FileScannerTask implements Runnable {

              
          private File root = null;

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

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

                  
          this.root = root;
              }

              @Override
              
          public void run() {
                  travleFiles(root);
              }

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

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

              
          public List<String> getFilePaths() {
                  
          return (List<String>) filePaths.clone();
              }
          }
          此處的文件掃描任務,提供了一個getFilePaths()方法以允許隨時都可以取出當前已掃描過的文件的路徑(相當于一個任務快照)。然后,創建一個針對該任務的Future類,如代碼清單2所示,
          清單2
          public class FileScannerFuture {

              
          private FileScannerTask task = null;

              
          public FileScannerFuture(FileScannerTask task) {
                  
          new Thread(task).start();
                  
          this.task = task;
              }

              
          public List<String> getResult() {
                  
          return task.getFilePaths();
              }
          }
          FileScannerFuture持有FileScannerTask的引用,并創建一個獨立的線程來執行該任務。在任務的執行過程中,應用程序可以在"未來"的某個時刻去獲取一個任務的快照,如代碼清單3所示,
          清單3
          public static void main(String[] args) throws Exception {
              FileScannerFuture future 
          = new FileScannerFuture(new FileScannerTask(new File("C:")));

              TimeUnit.SECONDS.sleep(
          1);
              List
          <String> filePaths1 = future.getResult();
              System.out.println(filePaths1.size());

              TimeUnit.SECONDS.sleep(
          1);
              List
          <String> filePaths2 = future.getResult();
              System.out.println(filePaths2.size());
          }

          2. 使用并發工具包中的Future實現
              前面所展示的Future實現十分的簡陋,沒有實際應用的意義。使用FileScannerFuture,應用程序在獲取filePaths時,無法得知其獲取的是否為最終結果,即無法判斷FileScannerTask是否已經完成。而且,也不能在必要時停止FileScannerTask的執行。毫無疑問,由JDK 5引入的并發工具包肯定會提供此類實用工具,如FutureTask。為了使用并發工具包中的Future,需要修改前述的FileScannerTask實現,讓其實現Callable接口,如代碼清單4所示,
          清單4
          public class FileScannerTask implements Callable<List<String>> {

              
          private File root = null;

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

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

                  
          this.root = root;
              }

              @Override
              
          public List<String> call() {
                  travleFiles(root);
                  
          return filePaths;
              }

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

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

              
          public List<String> getFilePaths() {
                  
          return (List<String>) filePaths.clone();
              }
          }
          應用程序也要相應的修改成如代碼清單5所示,使用ExecutorService來提交任務,并創建一個Future/FutureTask實例。
          清單5
          public static void main(String[] args) {
              ExecutorService executorService 
          = Executors.newCachedThreadPool();
              Future
          <List<String>> future = executorService.submit(new FileScannerTask(new File("C:")));

              
          try {
                  List
          <String> filePaths = future.get();
                  System.out.println(filePaths.size());
              } 
          catch (InterruptedException e) {
                  e.printStackTrace();
              } 
          catch (ExecutionException e) {
                  e.printStackTrace();
              }

              executorService.shutdown();
          }
          此處就是調用Future.get()方法來獲取任務的執行結果,如果任務沒有執行完畢,那么該方法將會被阻塞。該Future實現的好處就是,正常情況下,只有在任務執行完畢之后才能獲取其結果,以保證該結果是最終執行結果。

          3. 使用Future取消任務
              Future除了定義有可獲取執行結果的get方法(get()以及get(long timeout, TimeUnit unit)),還定義了三個方法:cancel(),isCancelled()以及isDone(),用于取消任務,以及判定任務是否已被取消、已執行完畢。如代碼清單6所示,
          清單6
          public interface Future<V> {

              
          boolean cancel(boolean mayInterruptIfRunning);
              
          boolean isCancelled();
              
          boolean isDone();
              
          }
          其中,cancel()方法中的boolean參數若為true,表示在取消該任務時,若執行該任務的線程仍在運行中,則對其進行中斷。如代碼清單7所示,若任務執行超時了,那么就取消它。
          清單7
          public static void main(String[] args) {
              ExecutorService executorService 
          = Executors.newCachedThreadPool();
              Future
          <List<String>> future = executorService.submit(new FileScannerTask(new File("C:")));

              
          try {
                  List
          <String> filePaths = future.get(1, TimeUnit.SECONDS);
                  System.out.println(filePaths.size());
              } 
          catch (InterruptedException e) {
                  e.printStackTrace();
              } 
          catch (ExecutionException e) {
                  e.printStackTrace();
              } 
          catch (TimeoutException e) {
                  e.printStackTrace();
              } 
          finally {
                  future.cancel(
          true);
              }

              executorService.shutdown();
          }
          在實際應用中,取消任務的原由肯定不僅僅只是超時這么簡單,還可能是由于接受到了用戶的指令。此時,則可能會從另一個獨立線程去取消該任務。除了取消任務之外,有時還需要取出任務中已經生成的部分結果。但為了能夠響應任務的退出,首先需要修改FileScannerTask,使得當任務被取消(中斷)時,任務能夠真正的快速停止并返回,如代碼清單8所示,
          清單8
          public class FileScannerTask implements Callable<List<String>> {

              

              
          private void travleFiles(File parent) {
                  
          if (Thread.currentThread().isInterrupted()) {
                      
          return;
                  }

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

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

              
          }
          相應地修改應用程序的代碼,如代碼清單9所示,
          清單9
          public static void main(String[] args) {
              ExecutorService executorService 
          = Executors.newCachedThreadPool();
              FileScannerTask task 
          = new FileScannerTask(new File("C:"));
              
          final Future<List<String>> future = executorService.submit(task);
              
              
          new Thread(new Runnable() {
                  
                  @Override
                  
          public void run() {
                      
          try {
                          TimeUnit.SECONDS.sleep(
          1);
                      } 
          catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      future.cancel(
          true);
                  }
              }).start();
              
              
          try {
                  List
          <String> filePaths = future.get();
                  System.out.println(filePaths.size());
              } 
          catch (InterruptedException e) {
                  e.printStackTrace();
              } 
          catch (ExecutionException e) {
                  e.printStackTrace();
              } 
          catch (CancellationException e) {
                  List
          <String> filePaths = task.getFilePaths();
                  System.out.println(
          "Partly result: " + filePaths.size());
              }
              
              executorService.shutdown();
          }
          由上可知,此處使用Future.cancel(true)的本質依然是利用了線程的中斷機制。

          4. 小結
              使用Future可以在任務啟動之后的特定時機再去獲取任務的執行結果。由JDK 5引入的并發工具包中提供的Future實現不僅可以獲取任務的執行結果,還可以用于取消任務的執行。
          posted on 2013-10-07 16:55 John Jiang 閱讀(3312) 評論(3)  編輯  收藏 所屬分類: JavaConcurrency原創Java并發基礎實踐

          評論

          # re: Java并發基礎實踐--退出任務II(原) 2013-10-08 11:04 xdemo.cn
          哪抄的?繼續繼續!  回復  更多評論
            

          # re: Java并發基礎實踐--退出任務II(原) 2013-10-08 16:22 Sha Jiang
          @xdemo.cn
          每個字,每行代碼都是我自己親自寫的:-(  回復  更多評論
            

          # re: Java并發基礎實踐--退出任務II(原)[未登錄] 2013-10-16 14:38 程序員
          @Sha Jiang
          厲害,期待寫更多的好文  回復  更多評論
            

          主站蜘蛛池模板: 蒙城县| 承德县| 清苑县| 施甸县| 革吉县| 陆丰市| 土默特右旗| 乡城县| 永宁县| 乌拉特中旗| 澄江县| 准格尔旗| 武乡县| 莱阳市| 鱼台县| 丘北县| 萨嘎县| 松原市| 获嘉县| 安化县| 金坛市| 手机| 安宁市| 高台县| 兴隆县| 靖州| 拉萨市| 浦北县| 朔州市| 饶平县| 平顶山市| 富顺县| 佛冈县| 九台市| 临沧市| 朝阳市| 邵阳市| 河北省| 肇东市| 遂昌县| 洞口县|