Java并发编程实战 - 任务执行与线程池总结

第六章 任务执行

I. 在线程中执行任务

  1. Task执行有两种错误的方式
  • 单线程内串行地执行任务。性能低,若任务有IO操作会block住工作线程使用CPU,响应能力差,吞吐量低。
  • 每接收一个任务就为它创建一个线程 ,无限制地创建线程。
  1. 无限制创建线程的缺陷
  • 线程生命周期管理的开销高。相比轻量级任务,线程创建和销毁的操作会显得很浪费。
  • 资源消耗,性能降低。大量的线程占用大量内存并会导致这些线程闲置,对CPU的竞争也会降低性能。
  • 服务稳定性。JVM上有线程总数量限制,超过会抛出OutOfMemoryError。

II. Executor框架

  1. 任务执行不应该和线程的管理耦合在一起。而Executor接口则是对任务提交与任务执行的抽象,它只有一个execute接口。Executor也是基于生产者/消费者模型的。
public interface Executor {
    void execute(Runnable command);
}
  1. Executors中的静态工厂方法可以来创建线程池:
  • newFixedThreadPool。创建固定长度的线程池,线程数量不到要求就会补。
  • newCachedThreadPool。根据需求动态调整线程数量。
  • newSingleThreadExecutor。单线程的Executor。
  • newScheduledThreadPool。创建固定长度的线程池,任务以延迟或定时的方式来执行。
  1. 在一个JVM中,如果Executor不关闭的话,JVM是无法退出的(只要JVM中存在用户工作线程,JVM即无法退出)。为了让Executor自行退出,引入ExecutorService加强生命周期管理
public interface ExecutorService extends Executor {

    /**
    * shutdown()
    * 平缓关闭线程池:不再接收新的任务,等待已经提交的任务跑完。
    */
    void shutdown();

    /**
    * shutdownNow()
    * 粗暴关闭线程池:停止全部任务,然后关闭线程池。
    */
    List shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
    // To-be-continued
}
  1. 对于延迟执行和周期执行的任务,Timer类表现不佳。因为Timer是单线程,各个TimerTask启动时间互相干扰,且TimerTask抛出异常会终止线程造成线程泄漏。应该使用DelayQueueScheduledThreadPoolExecutor来构建调度服务。

III. 找出可利用的并行性

  1. Executor框架只用Runnable的问题在于不能返回值或抛出受检异常,所以引入Callable来做到以上两点,以描述一个带返回值的任务。而Future则表示对这个任务生命周期的管理。一般来讲,submit(Callable)会立即返回Future
public interface Callable {
    V call() throws Exception;
}

public interface Future {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    /**
    * 阻塞直到任务完成。抛出执行Exception的话可以在ExecutionException中拿到。
    */
    V get() throw InterruptedException, ExecutionException, CancellationException;
    V get(long timeout, TimeUnit unit) throw InterruptedException, ExecutionException, CancellationException;
}
  1. 在实现中,提交Callable会被转成FutureTask返回给客户端,也提交给Executor.execute去执行。而FutureTask在客户端作为Future,在线程池端作为Runnable。
  2. 如果需要在运行一组Callable后立即得到结果,可以用一个ExecutorCompletionService来take,阻塞地获得新鲜出炉的结果。其原理是ExecutorCompletionService包装了一个Executor的实例,然后每个执行结束的Task会把结果放进ExecutorCompletionService的BlockingQueue里。类似于装饰器模式。
class ExecutorCompletionService implements CompletionService {
    
    // ...
    private class QueueingFuture extends FutureTask {
        QueueingFuture(RunnableFuture task) {
            super(task, null);
            this.task = task;
        }
        protected void done() { completionQueue.add(task); }
        private final Future task;
    }

    public ExecutorCompletionService(Executor executor) {
        if (executor == null)
            throw new NullPointerException();
        this.executor = executor;
        this.aes = (executor instanceof AbstractExecutorService) ?
            (AbstractExecutorService) executor : null;
        this.completionQueue = new LinkedBlockingQueue>();
    }
}
  1. 提交的任务可以超时取消,利用Future的get时的timeout参数,会抛出TimeoutException。然后在这里面cancel future。

第七章 取消与关闭

Java没有提供可以安全地终止线程的机制。但是可以通过中断这种写作文机制使一个一个线程终止另外一个线程的工作。

I. 任务取消

  1. 最朴素的使线程退出的方法是set一个boolean,工作的时候定时轮询这个值来判断是否应该中止。但这样的问题是,无法响应阻塞
  2. 应该使用中断的方法取消线程,当中断发生时,会设置中断状态,并在阻塞操作时抛出InterruptedException。
public class Thread {
    /**
    * 中断线程,使阻塞操作抛出Exception
    */
    public void interrupt() {/* */}
    public boolean isInterrupted() {/* */}
    /**
    * 清除当前线程的取消状态
    */
    public static boolean interrupted() {/* */}
}

你可能感兴趣的:(Java并发编程实战 - 任务执行与线程池总结)