任務取消與關閉

任務取消

Java沒有提供任何機制來安全地終止線程。但它提供了中斷(Interruption),這是一種協作機制,能夠使一個線程終止另一個線程的當前工作。

設置“已請求取消(Cancellation Requested)”標誌。

示例代碼:

public class PrimeGenerator implements Runnable {
    private volatile boolean cancelled;
    private final List<BigInteger> primes = new ArrayList<>();

    @Override
    public void run() {
        BigInteger p = BigInteger.ONE;
        while (!cancelled) {
            p = p.nextProbablePrime();
            synchronized (this) {
                primes.add(p);
            }
        }
    }

    public void cancel() {
        cancelled = true;
    }

    public synchronized List<BigInteger> get() {
        return new ArrayList<>(primes);
    }

    public static void main(String[] args) throws InterruptedException {
        PrimeGenerator pg = new PrimeGenerator();
        new Thread(pg).start();
        try {
            SECONDS.sleep(1);
        } finally {
            pg.cancel();
        }
        List res = pg.get();
        System.out.println("Size: " + res.size() + " " + res);
    }
}

通常,中斷是實現取消的最合理方式。Thread中的中斷方法如下:

public class Thread {
    /**
     * 中斷目標線程,即自己
     **/
	public void interrupt() {...}
	/**
     * 返回目標線程的中斷狀態
     **/
	public boolean isInterrupted() {...}
	/**
     * 清除當前線程的中斷狀態,並返回它之前的值
    **/
	public static boolean interrupted() {...}
}

示例代碼:

public class NewPrimeGenerator extends Thread {
    private final BlockingQueue<BigInteger> queue = new ArrayBlockingQueue<BigInteger>(1000000);

    @Override
    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            queue.put(p);
            while (!Thread.currentThread().isInterrupted()) {
                p = p.nextProbablePrime();
                queue.put(p);
            }
        } catch (InterruptedException e) {
            System.out.println("Interrupted exit....");
        } catch (Exception a) {
            System.out.println("Other Exception" + a);
        }
    }

    public void cancel() {
        interrupt();
    }

    public List<BigInteger> get() {
        return new ArrayList<>(queue);
    }


    public static void main(String[] args) throws InterruptedException {
        NewPrimeGenerator npg = new NewPrimeGenerator();
        npg.start();
        try {
            SECONDS.sleep(1);
        } finally {
            npg.cancel();
        }
        List res = npg.get();
        System.out.println("Size: " + res.size() + " " + res);
    }
}

通過Future來實現取消

Future擁有一個cancel方法,該方法帶有一個boolean類型的參數mayInterruptIfRunning,表示取消操作是否可以成功。

示例代碼:

public static void timedRun(Runnable r, long timeout, TimeUnit unit) throws InterruptedException {
    Future<?> task = taskExec.submit(r);
    try {
        task.get(timeout, unit);
    } catch (TimeoutException e) {
        //任務將被取消
    } catch (ExecutionException e) {
        throw launderThrowable(e.getCause());
    } finally {
        task.cancel(true);//如果任務正在運行,則將被中斷,否則直接取消
    }
}

封裝非標準的取消操作

如果一個線程由於執行同步的Socket I/O或者等待獲得內置鎖而阻塞,那麼中斷請求只能設置線程的中斷狀態,除此之外沒有其他任何作用。

示例代碼:

public class ReaderThread extends Thread {
    private final Socket socket;
    private final InputStream in;
    private final int BUFSIZ = 1024;

    private ReaderThread(Socket socket) throws IOException {
        this.socket = socket;
        this.in = socket.getInputStream();
    }

    @Override
    public void interrupt() {
        try {
            socket.close();
        } catch (IOException e) {
        } finally {
            super.interrupt();
        }
    }

    @Override
    public void run() {
        try {
            byte[] buf = new byte[BUFSIZ];
            while (true) {
                int count = in.read(buf);
                if (count < 0)
                    break;
                else if (count > 0)
                    processBuffer(buf, count);
            }
        } catch (IOException e) {
        }
    }

    private void processBuffer(byte[] buf, int count) {
        //some work done
    }
}

任務關閉

通過解決競態條件問題提供可靠關閉操作。

代碼:

public class LogService {
    private final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(Integer.MAX_VALUE);
    private final LoggerThread loggerThread = new LoggerThread();
    private final PrintWriter writer;
    private boolean isShutdown = false;
    private int reservations = 0;

    public LogService(PrintWriter writer) { this.writer = writer; }

    public void start() { loggerThread.start(); }
    
    public void stop() {
        synchronized (this) { isShutdown = true; }
        loggerThread.interrupt();
    }

    public void log(String msg) throws InterruptedException {
        synchronized (this) {
            if (isShutdown)
                throw new IllegalStateException("Service shutdown!");
            ++reservations;
        }
        queue.put(msg);
    }
    
    private class LoggerThread extends Thread {
        public void run() {
            try {
                while (true) {
                    try {
                        synchronized (LogService.this) {
                            if (isShutdown && reservations == 0)
                                break;
                        }
                        String msg = queue.take();
                        synchronized (LogService.this) {
                            --reservations;
                        }
                        writer.println(msg);
                    } catch (InterruptedException e) {
                        // retry?
                    }
                }
            } finally {
                writer.close();
            }
        }
    }
}

使用shutdown以及shutdownNow方法,關閉ExecutorService

示例代碼:

public class NewLogService {
    private final ExecutorService exec = Executors.newSingleThreadExecutor();
    private PrintWriter writer;

    public NewLogService(PrintWriter writer) {
        this.writer = writer;
    }

    public void start() {
    }

    public void stop() throws InterruptedException {
        try {
            exec.shutdown();
            exec.awaitTermination(10, TimeUnit.SECONDS);
        } finally {
            writer.close();
        }
    }
    public void log(String msg) {
        try {
            exec.execute(new WriteTask(msg));
        } catch (RejectedExecutionException e) {
        }
    }
}

“毒丸”對象

生產者再提交了“毒丸”對象後,將不會提交其他工作,消費者接收到“毒丸”對象之前完成隊列中的所有工作

示例代碼:

public class IndexingService {
    private static final File POISON = new File("");
    private final IndexerThread consumer = new IndexerThread();
    private final CrawlerThread producer = new CrawlerThread();
    private final BlockingQueue<File> queue = new ArrayBlockingQueue<File>(Integer.MAX_VALUE);
    private final FileFilter fileFilter;
    private final File root;

    public IndexingService(FileFilter fileFilter, File root) {
        this.fileFilter = fileFilter;
        this.root = root;
    }

    class CrawlerThread extends Thread {
        @Override
        public void run() {
            try {
                crawl(root);
            } catch (Exception e) {
            } finally {
                while (true) {
                    try {
                        queue.put(POISON);
                        break;
                    } catch (InterruptedException e) {
                        //retry
                    }
                }
            }
        }

        private void crawl(File root) { /* dig */ }

    }

    class IndexerThread extends Thread {
        @Override
        public void run() {
            try {
                while (true) {
                    File file = queue.take();
                    if (POISON == file) {
                        break;
                    } else {
                        indexFile(file);
                    }
                }
            } catch (Exception e) {
            } finally {
                while (true) {
                    try {
                        queue.put(POISON);
                        break;
                    } catch (InterruptedException e) {
                        //retry
                    }
                }
            }
        }

        private void indexFile(File file) { /* index */ }

    }
}

未捕獲異常的處理

在Thread API中提供了UncaughtExceptionHandler,它能檢測出某個線程由於未捕獲的異常而終結的情況。只有通過execute提交的任務,才能將他拋出的異常交給未捕獲異常處理器,而通過submit提交的任務,無論是拋出的未檢查異常還是已檢查異常,都將被認爲是任務返回狀態的一部分。

示例代碼:

public class UEHTest {
    private static class MyUEH implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            Logger logger = Logger.getAnonymousLogger();
            logger.log(Level.SEVERE, "Thread terminated with exception: " + t.getName(), e);
        }
    }

    private static class MyThread extends Thread {
        @Override
        public void run() {
            Integer.parseInt("aaa");
        }
    }

    public static void main(String[] args) {
        Thread myThread = new MyThread();
        myThread.setUncaughtExceptionHandler(new MyUEH());
        myThread.start();
        //Not working
        /*ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(myThread);*/
}

}

示例代碼:

public class UEHExecutorService {

    private static class MyUEH implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            Logger logger = Logger.getAnonymousLogger();
            logger.log(Level.SEVERE, "Thread terminated with exception: " + t.getName(), e);
        }
    }

    private static class MyThreadFactory implements ThreadFactory {
        private final Thread.UncaughtExceptionHandler handler = new MyUEH();

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setUncaughtExceptionHandler(handler);
            return thread;
        }
    }

    public static void main(String[] args) {
        ThreadFactory factory = new MyThreadFactory();
        ExecutorService exec = new ThreadPoolExecutor(10, 100, 180, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), factory);
        Thread thread = factory.newThread(new Runnable() {
            @Override
            public void run() {
                Integer.parseInt("abc");
            }
        });
        exec.execute(thread);
    }
}

參考:《Java併發編程實戰》、爲線程池中的每個線程設置UncaughtExceptionHandler線程池執行UncaughtExceptionHandler失效問題分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章