java并发编程7

java没有提供任何机制来安全地终止线程,但提供了中断,这是一种协作机制。

7.1 任务取消

用一个volatile类型的域保存取消状态,这应该是最基本的做法了

@ThreadSafe
public class PrimeGenerator implements Runnable {
    private static ExecutorService exec = Executors.newCachedThreadPool();

    @GuardedBy("this") private final List primes
            = new ArrayList();
    private volatile boolean cancelled;

    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 get() {
        return new ArrayList(primes);
    }

    static List aSecondOfPrimes() throws InterruptedException {
        PrimeGenerator generator = new PrimeGenerator();
        exec.execute(generator);
        try {
            SECONDS.sleep(1);
        } finally {
            generator.cancel();
        }
        return generator.get();
    }
}

取消策略

  • How:如何取消任务。
  • When:何时检查是否请求了取消。
  • What:响应取消请求时应该执行哪些操作。

7.1.1 中断

上述机制存在问题:如果调用了一个阻塞方法,那么可能永远不会检查取消标志。

class BrokenPrimeProducer extends Thread {
    private final BlockingQueue queue;
    private volatile boolean cancelled = false;

    BrokenPrimeProducer(BlockingQueue queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!cancelled)
                queue.put(p = p.nextProbablePrime());
        } catch (InterruptedException consumed) {
        }
    }

    public void cancel() {
        cancelled = true;
    }
}

如果queue.put因为队列满阻塞了,而消费者这时取消了任务就悲剧了。
所以,来看看中断,Thread中有一些中断方法,例如Interrupt(当然不是立刻停止目标线程,而是传递了请求中断的信息),and阻塞库方法会检查线程何时中断,所以,BrokenPrimeProducer的问题其实很好解决。

public class PrimeProducer extends Thread {
    private final BlockingQueue queue;

    PrimeProducer(BlockingQueue queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!Thread.currentThread().isInterrupted())
                queue.put(p = p.nextProbablePrime());
        } catch (InterruptedException consumed) {
            /* Allow thread to exit */
        }
    }

    public void cancel() {
        interrupt();
    }
}

感觉其实是Thread和类库方法自己做了检查状态的操作,你在外边做不到。

7.1.2 中断策略

这一节有点绕,大概就是强调了中断策略的重要性,以及中断操作应该由谁来做(调用方)。

7.1.3 响应中断

  • 传递异常,不捕获直接throws就好了
  • 恢复中断状态,Thread.currentThread().interrupt()

7.1.4 示例:计时运行

一种错误的做法:

private static final ScheduledExecutorService cancel Exec = ...;
public static void timeRun(Runnable r, long timeout, TimeUnit unit) {
  final Thread taskThread = Thread.currentThread();
  cancelExec.shedule(new Runnable() {
    publick void run() { taskThread.interrupt(); }
  }, timeout, unit);
  r.run();
}

它破坏了以下规则:在中断线称之前,应该了解它的中断策略。
r.run()怎么运行?何时结束?interrupt发生在run返回之后会怎样?如果run不响应中断呢?
一种解决办法:join

public class TimedRun2 {
    private static final ScheduledExecutorService cancelExec = newScheduledThreadPool(1);

    public static void timedRun(final Runnable r,
                                long timeout, TimeUnit unit)
            throws InterruptedException {
        class RethrowableTask implements Runnable {
            private volatile Throwable t;

            public void run() {
                try {
                    r.run();
                } catch (Throwable t) {
                    this.t = t;
                }
            }

            void rethrow() {
                if (t != null)
                    throw launderThrowable(t);
            }
        }

        RethrowableTask task = new RethrowableTask();
        final Thread taskThread = new Thread(task);
        taskThread.start();
        cancelExec.schedule(new Runnable() {
            public void run() {
                taskThread.interrupt();
            }
        }, timeout, unit);
        taskThread.join(unit.toMillis(timeout));
        task.rethrow();
    }
}

不过join也有一点问题:无法知道线程是正常退出还是join超时(这个例子里好像看不出有什么影响?)

7.1.5 通过Future来实现取消

public class TimedRun {
    private static final ExecutorService taskExec = Executors.newCachedThreadPool();

    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) {
            // task will be cancelled below
        } catch (ExecutionException e) {
            // exception thrown in task; rethrow
            throw launderThrowable(e.getCause());
        } finally {
            // Harmless if task already completed
            task.cancel(true); // interrupt if running
        }
    }
}

注意cancel的参数,一个boolean类型的mayInterruptIfRunning,如果为true并且任务在线程中运行,那么中断。false意味着如果任务还没启动,就不要运行它。

7.1.6 处理不可中断的阻塞

OK,到目前为止,我们处理的都是可以中断的操作(while(!cancel)),如果不可中断呢?例如:

  • Socket IO,关闭socket,抛出SocketException。
  • 同步IO,用InterruptibleChannel。
  • Selector的异步IO,close或wakeup,抛出ClosedSelectorException。
  • 获取某个锁,lockInterruptibly。
    拿Sokcket举例:
public class ReaderThread extends Thread {
    private static final int BUFSZ = 512;
    private final Socket socket;
    private final InputStream in;

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

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

    public void run() {
        try {
            byte[] buf = new byte[BUFSZ];
            while (true) {
                int count = in.read(buf);
                if (count < 0)
                    break;
                else if (count > 0)
                    processBuffer(buf, count);
            }
        } catch (IOException e) { /* Allow thread to exit */
        }
    }

    public void processBuffer(byte[] buf, int count) {
    }
}

7.1.7 采用newTaskFor来封装非标准的取消

进一步优化ReaderThread,有点复杂,先记下来吧。

public abstract class SocketUsingTask  implements CancellableTask {
    @GuardedBy("this") private Socket socket;

    protected synchronized void setSocket(Socket s) {
        socket = s;
    }

    public synchronized void cancel() {
        try {
            if (socket != null)
                socket.close();
        } catch (IOException ignored) {
        }
    }

    public RunnableFuture newTask() {
        return new FutureTask(this) {
            public boolean cancel(boolean mayInterruptIfRunning) {
                try {
                    SocketUsingTask.this.cancel();
                } finally {
                    return super.cancel(mayInterruptIfRunning);
                }
            }
        };
    }
}


interface CancellableTask  extends Callable {
    void cancel();

    RunnableFuture newTask();
}


@ThreadSafe
class CancellingExecutor extends ThreadPoolExecutor {
    public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public CancellingExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    protected  RunnableFuture newTaskFor(Callable callable) {
        if (callable instanceof CancellableTask)
            return ((CancellableTask) callable).newTask();
        else
            return super.newTaskFor(callable);
    }
}

7.2 停止基于线程的服务

7.2.1 示例:日志服务

public class LogWriter {
    private final BlockingQueue queue;
    private final LoggerThread logger;
    private static final int CAPACITY = 1000;

    public LogWriter(Writer writer) {
        this.queue = new LinkedBlockingQueue(CAPACITY);
        this.logger = new LoggerThread(writer);
    }

    public void start() {
        logger.start();
    }

    public void log(String msg) throws InterruptedException {
        queue.put(msg);
    }

    private class LoggerThread extends Thread {
        private final PrintWriter writer;

        public LoggerThread(Writer writer) {
            this.writer = new PrintWriter(writer, true); // autoflush
        }

        public void run() {
            try {
                while (true)
                    writer.println(queue.take());
            } catch (InterruptedException ignored) {
            } finally {
                writer.close();
            }
        }
    }
}

一个使用阻塞队列的简单示例。缺少完备的关闭机制。
补全:

public class LogService {
    private final BlockingQueue queue;
    private final LoggerThread loggerThread;
    private final PrintWriter writer;
    @GuardedBy("this") private boolean isShutdown;
    @GuardedBy("this") private int reservations;

    public LogService(Writer writer) {
        this.queue = new LinkedBlockingQueue();
        this.loggerThread = new LoggerThread();
        this.writer = new PrintWriter(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(/*...*/);
            ++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();
            }
        }
    }
}

7.2.2 关闭ExecutorService

将管理线程的工作委托给一个ExecutorService

7.2.3 “毒丸”对象

public class IndexingService {
   private static final int CAPACITY = 1000;
   private static final File POISON = new File("");
   private final IndexerThread consumer = new IndexerThread();
   private final CrawlerThread producer = new CrawlerThread();
   private final BlockingQueue queue;
   private final FileFilter fileFilter;
   private final File root;

   public IndexingService(File root, final FileFilter fileFilter) {
       this.root = root;
       this.queue = new LinkedBlockingQueue(CAPACITY);
       this.fileFilter = new FileFilter() {
           public boolean accept(File f) {
               return f.isDirectory() || fileFilter.accept(f);
           }
       };
   }

   private boolean alreadyIndexed(File f) {
       return false;
   }

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

       private void crawl(File root) throws InterruptedException {
           File[] entries = root.listFiles(fileFilter);
           if (entries != null) {
               for (File entry : entries) {
                   if (entry.isDirectory())
                       crawl(entry);
                   else if (!alreadyIndexed(entry))
                       queue.put(entry);
               }
           }
       }
   }

   class IndexerThread extends Thread {
       public void run() {
           try {
               while (true) {
                   File file = queue.take();
                   if (file == POISON)
                       break;
                   else
                       indexFile(file);
               }
           } catch (InterruptedException consumed) {
           }
       }

       public void indexFile(File file) {
           /*...*/
       };
   }

   public void start() {
       producer.start();
       consumer.start();
   }

   public void stop() {
       producer.interrupt();
   }

   public void awaitTermination() throws InterruptedException {
       consumer.join();
   }
}

只有在生产者和消费者的数量都已知的情况下,才可以使用“毒丸”对象。
只有在无界队列中,“毒丸”对象才能可靠的工作。

7.2.4 示例:只执行一次的服务

public class CheckForMail {
    public boolean checkMail(Set hosts, long timeout, TimeUnit unit)
            throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        final AtomicBoolean hasNewMail = new AtomicBoolean(false);
        try {
            for (final String host : hosts)
                exec.execute(new Runnable() {
                    public void run() {
                        if (checkMail(host))
                            hasNewMail.set(true);
                    }
                });
        } finally {
            exec.shutdown();
            exec.awaitTermination(timeout, unit);
        }
        return hasNewMail.get();
    }

7.2.5 shutdownNow的局限性

shudownNow会尝试取消正在执行的任务,并返回所有已提交但尚未开始的任务。问题是,我们无法知道哪些被取消执行的任务。通过封装ExecutorService并使得execute记录哪些任务是在关闭后取消的。

public class TrackingExecutor extends AbstractExecutorService {
    private final ExecutorService exec;
    private final Set tasksCancelledAtShutdown =
            Collections.synchronizedSet(new HashSet());

    public TrackingExecutor(ExecutorService exec) {
        this.exec = exec;
    }

    public void shutdown() {
        exec.shutdown();
    }

    public List shutdownNow() {
        return exec.shutdownNow();
    }

    public boolean isShutdown() {
        return exec.isShutdown();
    }

    public boolean isTerminated() {
        return exec.isTerminated();
    }

    public boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException {
        return exec.awaitTermination(timeout, unit);
    }

    public List getCancelledTasks() {
        if (!exec.isTerminated())
            throw new IllegalStateException(/*...*/);
        return new ArrayList(tasksCancelledAtShutdown);
    }

    public void execute(final Runnable runnable) {
        exec.execute(new Runnable() {
            public void run() {
                try {
                    runnable.run();
                } finally {
                    if (isShutdown()
                            && Thread.currentThread().isInterrupted())
                        tasksCancelledAtShutdown.add(runnable);
                }
            }
        });
    }
}

使用TrackingExecutor的一个例子

public abstract class WebCrawler {
    private volatile TrackingExecutor exec;
    @GuardedBy("this") private final Set urlsToCrawl = new HashSet();

    private final ConcurrentMap seen = new ConcurrentHashMap();
    private static final long TIMEOUT = 500;
    private static final TimeUnit UNIT = MILLISECONDS;

    public WebCrawler(URL startUrl) {
        urlsToCrawl.add(startUrl);
    }

    public synchronized void start() {
        exec = new TrackingExecutor(Executors.newCachedThreadPool());
        for (URL url : urlsToCrawl) submitCrawlTask(url);
        urlsToCrawl.clear();
    }

    public synchronized void stop() throws InterruptedException {
        try {
            saveUncrawled(exec.shutdownNow());
            if (exec.awaitTermination(TIMEOUT, UNIT))
                saveUncrawled(exec.getCancelledTasks());
        } finally {
            exec = null;
        }
    }

    protected abstract List processPage(URL url);

    private void saveUncrawled(List uncrawled) {
        for (Runnable task : uncrawled)
            urlsToCrawl.add(((CrawlTask) task).getPage());
    }

    private void submitCrawlTask(URL u) {
        exec.execute(new CrawlTask(u));
    }

    private class CrawlTask implements Runnable {
        private final URL url;

        CrawlTask(URL url) {
            this.url = url;
        }

        private int count = 1;

        boolean alreadyCrawled() {
            return seen.putIfAbsent(url, true) != null;
        }

        void markUncrawled() {
            seen.remove(url);
            System.out.printf("marking %s uncrawled%n", url);
        }

        public void run() {
            for (URL link : processPage(url)) {
                if (Thread.currentThread().isInterrupted())
                    return;
                submitCrawlTask(link);
            }
        }

        public URL getPage() {
            return url;
        }
    }
}

TrackingExecutor存在一个不可避免的竞态条件:一些被任务取消的任务实际上已经执行完成。原因:在任务执行最后一条指令以及线程池将任务记录为“结束”两个时刻之间,线程池关闭。如果任务是幂等的那就无所谓,否则要考虑。

7.3 处理非正常的线程终止

ThreadPoolExecutor和Swing使用的技术:直接捕获Throwable,确保行为糟糕的任务不会影响到后续任务的执行。
未捕获异常的处理:
当一个线程由于未捕获异常而退出时,JVM会把这个事件报告给应用程序提供的UNcaughtExceptionHandler。默认就是输出到System.err

7.4 JVM关闭

你可能感兴趣的:(java并发编程7)