JUC_5 之 java线程与常用线程池体系

480 阅读6分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

线程实现的基本知识

继承Thread

  1. 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务,因此把run()方法称为执行体
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程。
public class DemoThread extends Thread {

    @Override
    public void run() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        new DemoThread().start();
    }
}

实现Runnable

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run方法的方法体同样是该线程的执行体
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
  3. 调用线程对象的start()方法来启动该线程
public class DemoThread02 implements Runnable {
    @Override
    public void run() {
        System.out.println("hello");
    }

    public static void main(String[] args) {
        DemoThread02 dt = new DemoThread02();
        new Thread(dt, "t1").start();
    }
}

实现Callable与Future

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
public interface Callable<V> {    
    V call() throws Exception;
}
  1. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()的返回值。 (FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口)

  2. 使用FutureTask对象作为Thread对象的target来创建并启动新线程

  3. 调用FutureTask对象的get()方法获得子线程执行结束后的返回值

public class DemoThread03 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 1;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        DemoThread03 dt = new DemoThread03();
        FutureTask<Integer> task = new FutureTask<>(dt);
        new Thread(task, "有返回值的线程").start();
        task.get();
    }
}

常用线程池体系结构

1629701854.jpg

  1. Executor:线程池顶级接口
  2. ExecutorService:线程池次顶级接口,对Executor做了一些扩展,增加了一些功能
  3. ScheduledExecutorService:对ExecutorService做了一些扩展,增加一些定时任务相关的功能
  4. AbstractExecutorService:抽象类,运用模板方法设计模式实现了一部分方法
  5. ThreadPoolExecutor:普通线程池类,包含一些最基本线程池操作相关的方法实现
  6. ScheduledThreadPoolExecutor:定时任务线程池类,用于实现定时任务相关功能
  7. ForkJoinPool:新型线程池类,java7中新增的线程池类,基于工作窃取理论实现,运用于大任务拆小任务,任务无限多的场景
  8. Executors:线程池工具类,定义了一些快速实现线程池的方法

Executor

抽象出来所有线程的基本执行方法execute(Runnable command)

//线程池顶级接口,定义了一个执行无返回值的方法
public interface Executor {

    /**
     * 根据Executor的实现判断,可能会在新线程、线程池、线程调用中执行
     */
    void execute(Runnable command);
}

ExecutorService

提供了对线程池操作的服务补充

public interface ExecutorService extends Executor {
    //关闭线程池,不再接受新的任务,但已提交的任务会执行完成
    void shutdown();

    /**
     * 立即关闭线程池,尝试停止正在运行的任务,未执行的任务将不再执行
     * 被迫停止及未执行的任务将以列表的形式返回
     */
    List<Runnable> shutdownNow();

    //检查线程池是否已关闭
    boolean isShutdown();

    //检查线程池是否已终止,只有在shutdown()或shutdownNow()之后调用才有可能为true
    boolean isTerminated();

    //在指定时间内线程池达到终止状态了才会返回true
    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;

    //执行有返回值的任务,任务的返回值为task.call()的结果
    <T> Future<T> submit(Callable<T> task);

    /**
     * 执行有返回值的任务,任务的返回值为这里传入的result
     * 当然只有当任务执行完成了调用get()时才会返回
     */
    <T> Future<T> submit(Runnable task, T result);

    /**
     * 执行有返回值的任务,任务的返回值为null
     * 当然只有当任务执行完成了调用get()时才会返回
     */
    Future<?> submit(Runnable task);

    // 批量执行任务,只有当这些任务都完成了这个方法才会返回
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
            throws InterruptedException;

    /**
     * 在指定时间内批量执行任务,未执行完成的任务将被取消
     * 这里的timeout是所有任务的总时间,不是单个任务的时间
     */
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
            throws InterruptedException;

    //返回任意一个已完成任务的执行结果,未执行完成的任务将被取消(完成一个,把其他的都取消)
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException;

    //在指定时间内如果有任务已完成,则返回任意一个已完成任务的执行结果,未执行完成的任务将被取消
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
}

ScheduledExecutorService

提供了定时任务的服务补充

public interface ScheduledExecutorService extends ExecutorService {
    /**
     * 在指定的延时后执行一次
     * @param delay 从现在开始到延时执行的时间
     */
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
    //在指定的延时后执行一次
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

    /**
     * 在指定的延时后首先执行一次,随后按照周期执行,不包含任务执行的时间
     * @param initialDelay 第一次执行时间
     * @param period 两次执行的时间间隔,不包含上一个任务执行时间
     * 第一次执行时间:initialDelay
     * 第二次执行时间:initialDelay+period
     * 第三次执行时间:initialDelay+period*2
     */
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

    /**
     * 在指定的延时后首先执行一次,随后按照指定延时重复执行,相当于包含任务执行的时间
     * @param initialDelay 第一次执行时间
     * @param delay 一个任务执行结束到另一个任务开始执行之间的延迟,延时以上一个任务结束开始计算
     */
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
}

ThreadPoolExecutor解析

ThreadPoolExecutor的5种状态

  • RUNNING:接收新任务和进程队列任务
  • SHUTDOWN:不接收新任务,但是接收进程队列任务
  • STOP:不接收新任务也不接收进程队列任务,并且打断正在进行中的任务
  • TIDYING:所有任务终止,待处理任务数量为0,线程转换为TIDYING,将会执行terminated钩子函数
  • TERMINATED:terminated()执行完成

状态之间的转换:

  • RUNNING -> SHUTDOWN:调用shutdown()方法
  • (RUNNING or SHUTDOWN)-> STOP:调用shutdownNow()方法
  • SHUTDOWN -> TIDYING:队列和线程池都是空的
  • STOP -> TIDYING:线程池为空
  • TIDYING -> TERMINATED:钩子函数terminated()执行完成

4种丢弃策略:

  • CallerRunsPolicy:调用execute()的线程中运行被拒绝的任务【背压】
  • AbortPolicy:抛RejectedExecutionException异常(默认的处理方式)
  • DiscardPolicy:悄悄丢弃拒绝的任务
  • DiscardOldestPolicy:丢弃最老的未处理任务,然后重新执行execute()方法

源码分析:

  • 向ThreadPool提交任务-execute()
  • 创建新线程-addWorker(Runnable firstTask, boolean core)
  • 线程的主循环Worker.runWorker(Worker w)
  • 从队列中获取排队的任务-getTask()
  • 线程结束-processWorkerExit(Worker w, boolean completedAbruptly)
  • shutdown()、shutdownNow()、tryTerminate()

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,主要用来在给定的延迟之后运行任务,或者定期执行任务:

  • 使用自定义任务类型ScheduledFutureTask,即使不需要调度的任务也会被认为是延时为0的延时任务
  • 使用自定义的队列DelayedWorkQueue,DelayQueue是无界队列,与ThreadPoolExecutor比较缺少容量的限制、corePoolSize、maximumPoolSize,有效的简化了一些执行机制

1630049345.jpg

任务队列DelayedWorkQueue

DelayedWorkQueue基于堆的数据结构(小顶堆),ScheduledFutureTask将索引记录在堆数组。

static class DelayedWorkQueue extends AbstractQueue<Runnable>
        implements BlockingQueue<Runnable> {
    //初始化数组大小
    private static final int INITIAL_CAPACITY = 16;
    //存储任务的数组
    private RunnableScheduledFuture<?>[] queue =
            new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
    //线程安全锁
    private final ReentrantLock lock = new ReentrantLock();
    //元素数量
    private int size = 0;
    //领导线程
    private Thread leader = null;
    //和lock对应的Condition用于线程的等待和唤醒
    private final Condition available = lock.newCondition();
}

主要方法分析:

  • 元素的添加操作--offer()
  • 调整数组大小--grow()
  • 元素的取出操作--take()
  • finishPoll()、remove()、siftUp()、siftDown()、poll()

任务对象ScheduledFutureTask

private class ScheduledFutureTask<V>
        extends FutureTask<V> implements RunnableScheduledFuture<V> {
    //任务序列号
    private final long sequenceNumber;
    //任务执行的时间单位是毫秒
    private long time;
    /**
     * 重复执行任务的周期
     *      正数表示固定周期执行
     *      负数表示固定延时执行
     *      0表示不是周期任务
     */
    private final long period;
    //this的包装任务,用于周期任务的提交
    RunnableScheduledFuture<V> outerTask = this;
    //延时队列的索引号
    int heapIndex;
}

主要方法分析:

  • 任务对象大小比较--compareTo(Delayed other)
  • run()、取消任务--cancel()