Java并发[二]

Java并发编程实战第二部分总结

Posted by 袁平 on November 2, 2018

前言

主要讲解了Executor框架


正文


一. Executor框架

  1. 线程池: 可以通过Executors中的静态工厂方法之一来创建一个线程池
  1. newFixedThreadPool: 创建一个固定长度的线程池, 每当提交一个任务时就创建一个线程, 直到达到线程池的最大数量, 这是线程池规模将不再变化(如果某个线程发生异常终止, 那么线程池会补充一个线程)
  2. newCachedThreadPool: 创建一个可缓存的线程池, 如果线程池的当前规模超过了处理需求时, 那么将回收空闲的线程, 而当需求增加时, 则可以添加新的线程, 线程池的规模不存在任何限制
  3. newSingleThreadExecutor: 一个单线程的Executor, 它创建单个工作者线程来执行任务, 如果这个线程异常结束, 会创建另一个线程来代替;newSingleThreadExecutor能确保依照任务在队列中的顺序来执行(如: FIFO, LIFO, 优先级)
  4. newScheduledThreadPool: 创建了一个固定长度的线程池, 而且以延迟或定时的方式来执行任务, 类似于Timer
  1. Executor生命周期: 为了解决执行服务的生命周期问题, Executor扩展了ExecutorService接口; ExecutorService的生命周期有三种状态: 运行, 关闭, 已终止; 提供了如下方法:
  1. shutdown(): 执行平缓的关闭过程, 不再接受新任务, 同时等待已经提交的任务执行完成(包括那些还未开始执行的任务)
  2. shutdownNow(): 执行粗暴的关闭过程, 尝试取消所有运行中的任务, 并且不再启动队列中尚未开始执行的任务
  3. awaitTermination(): 等待ExecutorService到达终止状态, 或者通过isTerminated()来轮循是否已经终止
  1. 延迟任务与周期任务: TimerScheduleThreadPoolExecutor
  1. Timer: 在执行所有定时任务时只会创建一个线程, 如果某个任务执行时间过长, 那么将破坏其他TimerTask的定时精确性; Timer线程并不捕获异常, 因此当TimerTask抛出未检查异常时将终止定时线程

考虑下列程序, 运行1秒之后结束, 而不是运行6秒; 同时还会抛出异常

public class OutOfTime {
    public static void main(String[] args) throws InterruptedException {
        Timer timer = new Timer();
        timer.schedule(new ThrowTask(), 1);
        SECONDS.sleep(1);
        timer.schedule(new ThrowTask(), 5);
        SECONDS.sleep(5);
    }

    public static class ThrowTask extends TimerTask {

        @Override
        public void run() {
            throw new RuntimeException();
        }
    }
}
  1. 携带结果的任务CallableFuture:
  1. Callable任务主入口点(即call)将返回一个值, 并可能抛出一个异常
  2. Future: 表示一个任务的生命周期, 并提供了相应的方法来判断是否已经完成或取消, 以及获取任务的结果和取消任务等

二者区别参见其提供方法:

public interface Callable<V> {
    V call() throws Exception;
}
public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

二. 中断

  1. 有三个中断方法: interrupt(), isInterrupted(), 静态的interrupted()(Thread.interrupted())

  2. interrupt()能中断目标线程, 而isInterrupt()能返回目标线程的中断状态, 静态的interrupted()将清除当前线程的中断状态并返回它之前的值; 这也是清除中断状态的唯一方法

  3. 阻塞库方法, 如: Thread.sleep(), Object.wait()等, 都检查线程何时中断, 它们在响应中断时执行的操作包括: 清除中断状态, 抛出InterruptedException

  4. 调用interrupt()并不意味着立即停止目标线程正在进行的工作, 而只是传递了请求中断的消息(由线程自己去决定如何处理中断)

  5. 中断是实现取消最合理的方式

  6. 响应中断:
    1. 传递异常
    2. 恢复中断状态
  7. 不可阻塞中断: 许多可阻塞的方法都是通过提前返回或者抛出InterruptedException来响应中断请求的; 但是并非所有的可阻塞方法或者阻塞机制都能响应中断, 有如下两种情况:
  1. 当线程执行同步的Socket I/O
  2. 等待获得内置锁而阻塞时: 但是Lock类中提供了lockInterruptibly方法来允许在等待一个锁的同时仍能响应中断

三. 守护线程

线程可以分为两种: 普通线程和守护线程; 在JVM启动时创建的所有线程中, ,除了主线程以外, 其他的线程都是守护线程(例如垃圾回收器以及其他执行辅助工作的线程); 当创建一个新线程时, 新线程将继承它的线程的守护状态

普通线程与守护线程的区别仅在于当线程退出时发生的操作, 当一个线程退出时, JVM会检查其他正在运行的线程, 如果这些线程都是守护线程, 那么JVM会正常退出操作, 当JVM停止时, 所有仍然存在的守护线程都将被抛弃–既不会执行finally代码块, 也不会执行回卷栈, 而JVM只是直接退出