程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java並發基礎實踐:退出任務I

Java並發基礎實踐:退出任務I

編輯:關於JAVA

計劃寫一個"Java並發基礎實踐"系列,算作本人對Java並發學習與實踐的簡單總結。本文是該系列的第一篇,介紹了退出並發任務的最簡單方法。

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

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 小結

本文介紹了三種簡單的退出並發任務的方法:停止線程;使用可取消任務;使用中斷。毫無疑問,停止線程是不可取的。使用可取消的任務時,要避免任務由於被阻塞而無法及時,甚至永遠無法被取消。一般地,恰當地使用中斷是取消任務的首選方式。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved