Java中的线程池

1.线程池原理

有时候,系统需要处理非常多的执行时间很短的请求,如果每一个请求都开启一个新线程的话,系统就要不断的进行线程的创建和销毁,有时花在创建和销毁线程上的时间会比线程真正执行的时间还长。而且当线程数量太多时,系统不一定能受得了。

使用线程池主要为了解决一下几个问题:

通过重用线程池中的线程,来减少每个线程创建和销毁的性能开销。
对线程进行一些维护和管理,比如定时开始,周期执行,并发数控制等等。

线程池的构图如下:

image-20210811225615028

整体的执行流程如下:

image-20210811225633752

  • 当正在运行的线程小于corePoolSize,线程池会创建新的线程。
  • 当大于corePoolSize而任务队列未满时,就会将整个任务塞入队列。
  • 当大于corePoolSize而且任务队列满时,并且小于maximumPoolSize时,就会创建新额线程执行任务。
  • 当大于maximumPoolSize时,会根据handler策略处理线程。

2.线程池的使用

2.1创建线程池

我们可以通过ThreadPoolExecutor来创建一个线程池。

它有四个重载方法,单最终都会调用最后一个构造方法:

image-20210810225258321

之后一个构造方法如下:

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

具体的参数分析如下:

  • corePoolSize

核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true

每次提交任务到线程池时,只要当前运行的线程数小于corePoolSize就会去创建新的线程去执行,无论此时有没有线程是空闲的。

如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有的基本线程。

  • maximumPoolSize

线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingQeque时,这个值无效。(即如果使用的是无界的任务队列这个参数就没啥效果,具体可看上面线程池原理部分)

  • keepAliveTime

非核心线程的闲置超时时间,超过这个时间就会被回收。

  • unit

指定keepAliveTime的单位,如TimeUnit.SECONDS。当将allowCoreThreadTimeOut设置为true时对corePoolSize生效。

  • workQueue

线程池中的任务队列.

常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。

  • threadFactory

线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法

public interface ThreadFactory {
  Thread newThread(Runnable r);
}

通过线程工厂可以对线程的一些属性进行定制。

默认工厂:

static class DefaultThreadFactory implements ThreadFactory {
  private static final AtomicInteger poolNumber = new AtomicInteger(1);
  private final ThreadGroup group;
  private final AtomicInteger threadNumber = new AtomicInteger(1);
  private final String namePrefix;

  DefaultThreadFactory() {
      SecurityManager var1 = System.getSecurityManager();
      this.group = var1 != null?var1.getThreadGroup():Thread.currentThread().getThreadGroup();
      this.namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
  }

  public Thread newThread(Runnable var1) {
      Thread var2 = new Thread(this.group, var1, this.namePrefix + this.threadNumber.getAndIncrement(), 0L);
      if(var2.isDaemon()) {
          var2.setDaemon(false);
      }

      if(var2.getPriority() != 5) {
          var2.setPriority(5);
      }

      return var2;
  }
}
  • RejectedExecutionHandler

RejectedExecutionHandler也是一个接口,只有一个方法

public interface RejectedExecutionHandler {
  void rejectedExecution(Runnable var1, ThreadPoolExecutor var2);
}

当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。

线程池中的各个参数

2.2向线程池提交任务

可以使用两个方法向线程池提交任务,分别是exccute()submit()

execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。execute()方法的入参是一个Runnable类的实例,代码如下:

threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                
            }
        });

或者lambda表达式的形式:

 threadPoolExecutor.execute(() -> {

        });

submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过futureget()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞当前线程一段时间后返回,这个时候可能任务还没完成。具体的使用见下面的demo:

// 返回值类型可以自己定
Future<?> submit = threadPoolExecutor.submit(() -> {
            // todo 这里写具体逻辑以及可以给个返回值
        });

        try {
            Object o = submit.get();
        } catch (InterruptedException e) {
            // 处理中断异常
            e.printStackTrace();
        } catch (ExecutionException e) {
            // 处理无法执行任务的异常
            e.printStackTrace();
        }

2.3关闭线程池

可以通过调用线程池的 shutdownshutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt()方法来中断线程,所以无法响应中断的任务可能永远无法终止。但它们存在一定的区别,shutdownNow首先是将线程池的状态设置为STOP,然后尝试停止所有的线程,并返回等待执行任务的列表;而shutdown只是将线程池的状态设置成SHUTDOWN,然后中断所有没有在执行任务的线程,等待所有任务执行成功之后线程池才会关闭。

只要调用上面两个方法中的任意一个,isShutdown方法就会返回true.当所有的任务都关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true.

还有boolean awaitTermination(long timeout, TimeUnit unit)方法可以在查看线程池结束:

当前线程阻塞,timeout 和 TimeUnit 两个参数,用于设定超时的时间及单位,当前线程阻塞,直到:

  • 等所有已提交的任务(包括正在跑的和队列中等待的)执行完;
  • 或者 等超时时间到了(timeout 和 TimeUnit设定的时间);
  • 或者 线程被中断,抛出InterruptedException

然后会监测 ExecutorService 是否已经关闭,返回true(所有任务执行完毕)或false(已超时)

优雅的关闭线程池源码解读

3.Executor框架

3.1Executor框架简介

Executor框架包括3大部分:

(1)任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;

(2)任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口的ExecutorService接口。

(3)异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。

Executor框架的成员及其关系可以用一下的关系图表示:

Executor框架 (1)

Executor框架的使用过程如下:

temp

主线程首先要创建实现Runnable或者Callable接口的任务对象。工具类Executors可以把一个Runnable对象封装为一个Callable对象(Executors.callable(Runnable task)或者Executors.callable(Runnable task, Object result))。

然后可以把Runnable对象直接交给ExecutorServer执行(ExecutorService.execute(Runnable command));或者可以把Runnable对象或者Callable对象提交给ExecutorService执行(ExecutorService.submit(Runnable task)ExecutorService.submit(Callable<T> task))。

如果执行ExecutorService.submit(...),ExecutorService将返回一个实现Future接口的对象(到目前为止的JDK中,最终返回的都是FutureTask对象)。由于FutureTask实现了Runnable,程序员也可以创建FutureTask,然后直接交给ExecutorService执行。

如果使用submit方法,Callable或者Runnable的实现对象中无返回值,此时调用get()方法只是等待任务执行完成,此时返回的Future对象是null

最后,主线程可以执行FutureTask.get()方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)来取消此任务的执行。

3.2Executor框架的成员

Executor框架的主要成员有:ThreadPoolExecutor、ScheduledThreadPoolExecutor、Future接口、Runnable接口、Callable接口和Executors

(1)ThreadPoolExecutor

ThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建3种类型的ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedT hreadPool。

下面分别介绍这3种ThreadPoolExecutor

  • FixedThreadPool

下面是Executors提供的,创建使用固定线程数的FixedThreadPool的API

public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

FixedThreadPool适用于为了满足资源管理的需求,而需要限制当前线程数量的场景。

  • SingleThreadExecutor

下面是Executors提供的,创建使用单线程的SingleThreadExecutor的API

public static ExecutorService newSingleThreadExecutor();
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) 

SingleThreadExecutor适用于需要保证顺序地执行各个人物,并且在任意时间点,不会有多个线程活动的应用场景

  • CachedThreadPool

下面是Executors提供的,创建一个会根据需要创建新线程的CachedThreadPool的API

(核心线程数为0,最大线程数为Integer.MAX_VALUE,队列为SynchronousQueue)

public static ExecutorService newCachedThreadPool();
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)

CachedThreadPool的线程最小为0,也就是没有核心线程,最大则为Integer.MAX_VALUE,可以认为是无限。活动时间是60s,60s线程不活动的话就会被回收,而SynchronousQueue是一个每次移除数据都要insert的(可以认为它只存一个)。它适用于执行很多的短期异步任务或者负载比较低的使用场景。

(2)ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor通常使用工厂类Executors来创建。Executors可以创建2种类型的ScheduledThreadPoolExecutor,如下:

  • ScheduledThreadPoolExecutor 包含若干个线程的ScheduledThreadPoolExecutor
  • SingleThreadScheduledPoolExecutor 只包含一个线程的ScheduledThreadPoolExecutor

下面是工厂类Executors提供的,创建固定个数线程的ScheduledThreadPoolExecutor的API

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory);

ScheduledThreadPoolExecutor适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数量的使用场景。

下面是工厂类Executors提供的,创建单个线程的ScheduledThreadPoolExecutor的API

public static ScheduledExecutorService newSingleThreadScheduledExecutor();
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory);

SingleThreadScheduledExecutor适用于需要单个后台线程执行周期任务,同时需要保证顺序地执行各个任务的应用场景。

(3)Future接口

Future接口和实现Future接口的FutureTask类用来表示异步计算的结果。当我们把Runnable接口或者Callable接口的实现类提交(submit)给ThreadPoolExecutor或者ScheduledThreadPoolExecutor时,ThreadPoolExecutor或者ScheduledThreadPoolExecutor会向我们返回一个FutureTask对象。下面是对应的API

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable<T> task, T result);
Future<?> submit(Runnable task);

有一点需要注意,到目前为止,Java通过上述API返回的是一个FutureTask对象。但是API可以看到,java仅仅保证返回的是一个实现了Future接口的对象。在将来的JDK视线中,返回的可能不一定是FutureTask。

(4)Runnable接口和Callable接口

Runnable接口或者Callable接口的实现类,都可以被ThreadPoolExecutor或者ScheduledThreadPoolExecutor执行。它们之间的区别就是Runnable不会返回结果,而Callable可以返回结果。

除了可以自己创建实现Callable接口的对象之外,还可以使用工厂类Executors来把一个Runnable包装成一个Callable。

下面是Executors提供的,把一个Runnable包装成一个Callable的API。

public static Callable<Object> callable(Runnable task); // 假设返回对象Callable1

下面是Executors提供的,把一个Runnabl和一个待返回的结果包装成一个Callable的API。

public static <T> Callable<T> callable(Runnable task, T result); // 假设返回对象Callable2

前面讲过,当我们把一个Callable对象(比如上面的Callable1或者Callable2)提交(submit)给ThreadPoolExecutor或者ScheduledThreadPoolExecutor执行时,submit(…)会向我们返回一个FutureTask对象。我们可以执行FutureTask.get()方法来等待任务执行完成。当任务执行成功完成后FutureTask.get()将返回该任务的接口。

例如:如果提交的是对象Callable1,FutureTask.get()方法将返回null;如果提交的是对象Callable2,FutureTask.get()方法将返回result对象。

3.3ThreadPoolExecutor详解

Executor框架最核心的类是ThreadPoolExecutor,他是线程池的实现类,主要由下列四个组件构成。

  • corePool:核心线程池的大小
  • maximumPool:最大线程池的大小
  • BlockingQueue:用来暂时保存任务的工作队列
  • RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和(达到了最大线程池大小且工作队列已满),execute()方法将要调用的Handler(submit()方法最终也会调用到execute()方法,具体可看源码)

通过Executor框架的工具类Executors,可以创建三种类型的ThreadPoolExecutor

  • FixedThreadPool
  • SingleThreadExecutor
  • CachedThreadPool
FixedThreadPool

FixedThreadPool被称为可重用固定线程数的线程池。下面是FixedThreadPool的源代码实现:

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

FixedThreadPool的corePoolSize和maximumPoolSize都被设置为创建时指定的nThreads。即所有的线程都是核心线程。

FixedThreadPool的execute()方法的运行示意图如下:

image-20210817203437080

  1. 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。

2)如果线程池完成预热之后(当前运行的线程数等于corePoolSize),将任务加入到LinkedBlockingQueue

3)线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。

FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响

  1. 当线程池中的数量达到corePoolSize后,新任务将在无界队列中等待。此时maximumPoolSize参数和keepAliveTime参数将是无效的参数
  2. 由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)(除非队列大小达到Integer.MAX_VALUE,这种基本不用考虑,内存都撑不住)
SingleThreadExecutor

SingleThreadExecutor是使用单个worker线程的Executor。下面是SingleThreadExecutor的源代码实现:

 public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1.其他参数与FixedThreadPool相同。SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。它带来的影响与FixedThreadPool相同,不再赘述。

SingleThreadExecutor的运行示意图如下:

image-20210817210145362

  1. 如果当前运行的线程数少于corePoolSize(即线程池中无运行的线程),则创建新线程来执行任务。

2)如果线程池完成预热之后(当前线程池中有一个运行的线程),将任务加入到LinkedBlockingQueue

3)线程执行完1中的任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。

CachedThreadPool

CachedThreadPool是一个会根据需要创建新线程的线程池。下面是CachedThreadPool的源代码:

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

CachedThreadPool的corePoolSize被设置为0,maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPoolSize是无界的。keepAliveTime设置为60L,意味着CachedThreadPool中空闲的线程等待新任务最长时间为60秒,空闲线程超过60秒后将被终止。CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列。

在极端状态下,CachedThreadPool会因为创建过多的线程而耗尽 CPU和内存资源。

CachedThreadPool的execute()方法示意图如下:

image-20210817230553377

1)首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime. TimeUnit.NANPSECONDS),那么主线程执行offer操作与空闲线程执行的pool操作配对成功,主线程把任务交给空闲线程执行,execute方法执行完成,否则执行下面的步骤2.

2)当初始maximumPool为空,或者maximumPool中当前没有空闲的线城市,将没有线程执行SynchronousQueue.poll(keepAliveTime. TimeUnit.NANPSECONDS)。这种情况下,步骤1将失败。此时CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成。

3)在步骤2中新创建的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime. TimeUnit.NANPSECONDS)。这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒。如果60秒内主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的新任务;否则,这个空闲线程将终止。由于空闲线程在空闲60秒后被终止,因此上时间报纸空闲的CachedThreadPool不会使用任何资源。

前面说过SynchronousQueue是一个没有容量的阻塞队列。每次插入必须等待另一个线程的对应移除操作,反之亦然。CachedThreadPool使用SynchronousQueue把提交的任务传递给空闲线程执行。CachedThreadPool中任务的传递示意图如下:

image-20210817231011234

3.4ScheduledThreadPoolExecutor详解

ScheduledThreadPoolExecutor继承自ThreadPoolExecutor。它主要用来在给定的延迟后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor的功能与Timer类似,但ScheduledThreadPoolExecutor的功能更加强大、更灵活。Timer对应的是单个后台进程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数。

ScheduledThreadPoolExecutor的运行机制

ScheduledThreadPoolExecutor的执行示意图如下图所示:

image-20210818232109062

DelayQueue是无界队列(内部使用的是数组,看源代码的grow()方法可以看到扩容时最大到Integer.MAX_VALUE),所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中没有什么意义,在构造方法中,这个值maximumPoolSize就是Integer.MAX_VALUE。

ScheduledThreadPoolExecutor的执行主要分为两大部分。

1)当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFuture接口的ScheduledFutureTask

2)线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。

ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改。

  • 使用DelayQueue作为任务队列
  • 获取任务的方式不同(实现中会说)
  • 执行周期任务后,增加了额外的处理(会修改ScheduledFutureTask的time变量再放回DelayQueue)
ScheduledThreadPoolExecutor的实现

前面我们提到过,ScheduledThreadPoolExecutor会把待调度的任务(ScheduledFutureTask)放到一个DelayQueue中。

ScheduledFutureTask主要包含3个成员变量,如下:

  • long 类型的成员变量time,表示这个任务将要被执行的具体时间。
  • long类型的成员变量sequenceNumber,表示这个任务被添加到scheduledThreadPoolExecutor中的序号。
  • long类型的成员变量period,表示任务执行的间隔周期

DelayQueue封装了一个PriorityQueue,这个PriorityQueue会对队列中的ScheduledFutureTask进行排序。排序时,time小的排在前面(时间早的任务将被先执行)。如果两个ScheduledFutureTask的time相同,就比较sequenceNumber,sequenceNumber小的排在前面(也就是说,如果两个任务的执行时间相同,那么先提交的任务将先执行)。

首先,我们看看ScheduledThreadPoolExecutor中的线程执行周期任务的过程。下图是ScheduledThreadPoolExecutor中的线程1执行某个周期任务的四个步骤。

image-20210819192338554

下面对4个步骤进行说明。

1)线程1从DelayQueue中获取已到期的ScheduledFutureTask(DelayQueue.take())。到期任务是指ScheduledFutureTask的time大于等于当前时间。

2)线程1执行这个ScheduledFutureTask。

3)线程1修改ScheduledFutureTask的time变量为下次将要被执行的时间

4)线程1把这个修改time之后的ScheduledFutureTask放回DelayQueue中(DelayQueue.add())。

接下来,让我们看看上面的步骤1获取任务的过程。下面是DelayQueue.take()方法的源代码实现。

 public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();                                                            
        try {
            for (;;) {
                E first = q.peek();
                if (first == null)
                    available.await();
                else {
                    long delay = first.getDelay(NANOSECONDS);
                    if (delay <= 0)
                        return q.poll();
                    first = null; // don't retain ref while waiting
                    if (leader != null)
                        available.await();                                                   
                    else {
                        Thread thisThread = Thread.currentThread();
                        leader = thisThread;
                        try {
                            available.awaitNanos(delay);                                   
                        } finally {
                            if (leader == thisThread)
                                leader = null;
                        }
                    }
                }
            }
        } finally {
            if (leader == null && q.peek() != null)
                available.signal();
            lock.unlock();                                                                 
        }
    }

获取任务步骤如下:

  1. 执行加锁操作
  2. 取出优先级队列元素q的队首
  3. 如果元素q的队首/队列为空,阻塞请求
  4. 如果元素q的队首(first)不为空,获得这个元素的delay时间值
  5. 如果first的延迟delay时间值为0的话,说明该元素已经到了可以使用的时间,调用poll方法弹出该元素,跳出方法
  6. 如果first的延迟delay时间值不为0的话,释放元素first的引用,避免内存泄露
  7. 判断leader元素是否为空,不为空的话阻塞当前线程
  8. 如果leader元素为空的话,把当前线程赋值给leader元素,然后阻塞delay的时间,即等待队首到达可以出队的时间,在finally块中释放leader元素的引用
  9. 循环执行从1~8的步骤
  10. 如果leader为空并且优先级队列不为空的情况下(判断还有没有其他后续节点),调用signal通知其他的线程
  11. 执行解锁操作

最后,我们看看ScheduledThreadPoolExecutor中的线程执行任务的步骤4,把ScheduledFutureTask放入DelayQueue中的过程。下面是DelayQueue.add()的源码实现:

 public boolean add(E e) {
        return offer(e);
    }

  public boolean offer(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            q.offer(e);
            if (q.peek() == e) {
                leader = null;
                available.signal();
            }
            return true;
        } finally {
            lock.unlock();
        }
    }

具体实现步骤如下:

  1. 执行加锁操作
  2. 把元素添加到优先级队列中
  3. 查看元素是否为队首
  4. 如果是队首的话,设置leader为空,唤醒所有等待的队列
  5. 释放锁

3.5FutureTask详解

Future接口和实现Future接口的FutureTask类,代表异步计算的结果。

FutureTask可以处于下面3种状态。

1)未启动。FutureTask.run()方法还没有被执行之前,FutureTask处于未启动状态。

2)已启动。FutureTask.run()方法被执行的过程中,FutureTask处于已启动的状态。

3)已完成。FutureTask.run()方法执行完后正常结束,或者被取消(FutureTask.cancel(…)),或者执行FutureTask.run()方法时抛出异常而异常结束,FutureTask处于已完成状态。

下面是FutureTask的状态迁移示意图:

image-20210818214049413

当FutureTask处于未启动或者已启动状态时,执行FutureTask.get()方法将导致调用线程阻塞;当FutureTask处于已完成状态时,执行FutureTask.get()方法将导致调用线程立即但会结果或者抛出异常。

当FutureTask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会被执行;当FutureTask处于已启动状态时,执行FutureTask.cancel(true)方法将以终端执行此任务线程的方式来试图停止任务;当FutureTask处于已启动状态时,执行FutureTask.cancel(false)方法将不会对正在之赐你个此任务的线程产生影响(让正在执行的任务运行完成);当FutureTask处于已完成状态时,执行FutureTask.cancel(…)方法将返回false.

下图是get方法和cancel方法的执行示意图:

image-20210818220151989

使用

可以吧FutureTask交给Executor执行;也可以通过ExecutorService.submit(…)方法返回一个FutureTask,然后执行FutureTask.get()方法或者FutureTask.cancel(…)方法。除此以外,还可以单独使用FutureTask。

实现

FutureTask的实现基于AQS。把自己的get()、run()、cancel()等方法委托给AQS(FutureTask内部类Sync继承了AQS)。具体实现逻辑不再赘述。

ask处于未启动状态时,执行FutureTask.cancel()方法将导致此任务永远不会被执行;当FutureTask处于已启动状态时,执行FutureTask.cancel(true)方法将以终端执行此任务线程的方式来试图停止任务;当FutureTask处于已启动状态时,执行FutureTask.cancel(false)方法将不会对正在之赐你个此任务的线程产生影响(让正在执行的任务运行完成);当FutureTask处于已完成状态时,执行FutureTask.cancel(…)方法将返回false.

下图是get方法和cancel方法的执行示意图:

image-20210818220151989

使用

可以吧FutureTask交给Executor执行;也可以通过ExecutorService.submit(…)方法返回一个FutureTask,然后执行FutureTask.get()方法或者FutureTask.cancel(…)方法。除此以外,还可以单独使用FutureTask。

实现

FutureTask的实现基于AQS。把自己的get()、run()、cancel()等方法委托给AQS(FutureTask内部类Sync继承了AQS)。具体实现逻辑不再赘述。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值