美文网首页Android知识Java学习笔记自定义views
java并发 —— Runnable、Callable、Futu

java并发 —— Runnable、Callable、Futu

作者: thinkChao | 来源:发表于2017-07-24 00:12 被阅读766次

这篇文章是对Java多线程中主要的几个类,包括Runnable、Callable、Future、FutureTask及Executor框架涉及的类做的一个梳理与介绍,介绍了它们之间的关系和功能,并没有深入探究。日后在需要的时候,再进行深入的学习和掌握。

Runnable、Callable

  • Rubbable
package java.lang;

public interface Runnable {

    public abstract void run();
    
}
  • Callable
package java.util.concurrent;

@FunctionalInterface
public interface Callable<V> {

    V call() throws Exception;
}

在开始一个新的线程之前,你必须指定由这个线程执行的代码,通常称为task。这可以通过实现Runnable、Callable实现。

其中Runnable应该是我们最熟悉的接口,从代码可以看出,它只有一个run()函数,用于将耗时操作写在其中,该函数没有返回值。然后使用某个线程去执行该runnable即可实现多线程,Thread类在调用start()函数后就是执行的是Runnable的run()函数。

Callable与Runnable的功能大致相似,Callable中有一个call()函数,但是call()函数有返回值

所以它俩的区别就在于回调函数是否有返回值,单独来讲他们跟线程是一点关系都没有的,就是一个接口而已。

Future

package java.util.concurrent;

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;
}

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果、设置结果操作。

Future提供了三种功能:

  • 判断任务是否完成
  • 能够中断任务
  • 能够获取任务执行结果

方法介绍如下:

  • cancel():法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。
  • isCancelled():表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
  • isDone():表示任务是否已经完成,若任务完成,则返回true。
  • get():用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回。
  • get(long timeout, TimeUnit unit):用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

在Future接口的代码中似乎看不到它和Runnable或者Callable有任何“纠缠”,那它又是如何获得这些结果的呢?毕竟它只是一个接口,实现就在FutureTask类中。

FutureTask

package java.util.concurrent;
import java.util.concurrent.locks.LockSupport;

public class FutureTask<V> implements RunnableFuture<V> {

   //代码省略
}

FutureTask实现了RunnableFuture接口,而RunnableFuture又是继承了Runnable和Future的,所以FutureTask就是实现了Runnable和Future的。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,管理任务。

其中有两个构造方法:

//接受一个 Callable 参数
   public FutureTask(Callable<V> callable) {
       if (callable == null)
           throw new NullPointerException();
       this.callable = callable;
       this.state = NEW;       // ensure visibility of callable
   }

   //接受一个 Runnable ,利用 Executors.callable 将Runnable 转换为Callable
   public FutureTask(Runnable runnable, V result) {
       this.callable = Executors.callable(runnable, result);
       this.state = NEW;       // ensure visibility of callable
   }

可以看到,Runnable也会通过Executors.callable()方法被转换为Callable类型,所以FutureTask最终都是执行Callable类型的任务,毕竟只有Callable是有返回值的,Runnable没有返回值。猜测里面应该是用了适配器模式这一设计模式。

Executor框架

简介

以上这些都与线程相关,用来实现任务的执行和结果的取回,但还未涉及到线程的调度执行,下面介绍的Executor框架就是用来完成这个操作的。

为什么要使用 Executor框架?

  • new Thread()的缺点
    每次new Thread()耗费性能 调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞争,会导致过多占用系统资源导致系统瘫痪。 不利于扩展,比如如定时执行、定期执行、线程中断。
  • 采用线程池的优点
    重用存在的线程,减少对象创建、消亡的开销,性能佳 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞 提供定时执行、定期执行、单线程、并发数控制等功能。

在Executor框架中,使用执行器(Exectuor)来管理Thread对象,从而简化了并发编程。并发编程的一种编程方式把任务拆分为一系列的小任务,即Runnable,然后将这些任务提交给一个Executor执行,Executor.execute(Runnalbe) ,Executor在执行时使用其内部的线程池来完成操作。该框架能够支持多种不同类型的任务执行策略,它提供了一种标准的方法将任务的提交过程与执行过程解耦开来。

先上张类图,搞清楚Executor框架的类继承关系(截图取自互联网):


Executor

Executor接口定义了一个接收Runnable对象的方法executor(Runnable command)

ExecutorService

是一个比Executor使用更广泛的子类接口,其提供了生命周期管理的方法,以及可跟踪一个或多个异步任务执行状况返回Future的方法。

ExecutorService的生命周期有3种状态:运行、关闭和已终止。

ExecutorService在初始创建时处于运行状态。

shutdown方法将执行平缓的关闭过程,不再接受新的任务,同时等待已经提交的任务执行完成,包括那些还未开始执行的任务。

shutdownNow方法将执行粗暴的关闭过程,它将尝试取消所有执行中的任务,并且不再启动队列中尚未开始执行的任务。

在所有任务都完成后,ExecutorService将转入终止状态。可以调用awaitTermination()来等待ExecutorService到达终止状态,或者通过调用isTerminated()来轮询ExecutorService是否已经终止。通常在调用awaitTermination()之后会立即调用shutdown(),从而产生同步地关闭ExecutorService的效果。

AbstractExecutorService

ExecutorService执行方法的默认实现。

ScheduledExecutorService

一个可定时调度任务的接口。

ThreadPoolExecutor

线程池,可以通过调用Executors的静态工厂方法来创建线程池并返回一个ExecutorService对象。

Executor框架的核心是线程池。线程池是指管理一组同构工作线程的资源池,在"线程池中执行任务"比"为每个任务分配一个线程"优势更多。通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程中产生的巨大开销。另一个额外的好处是,当请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性。通过适当调整线程池大小,可以创建足够多的线程以便使处理器保持忙碌,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存而失败。

ThreadPoolExecutor定义了很多构造函数,以下代码是该类最重要的构造函数:

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

参数说明:

corePoolSizemaximumPoolSizekeepAliveTime以及unit这几个参数分别定义了线程池的基本大小、最大大小以及存活时间。corePoolSize定义了线程池的基本大小,也就是线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。maximumPoolSize定义了线程池的最大大小,表示线程池可同时活动线程数量上限。keepAliveTime和unit共同定义了线程的存活时间,如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过基本大小时,这个线程将被终止。

workQueue参数包含Runnable的阻塞队列,当线程池达到基本大小时,新提交的任务将放入这个阻塞队列中,阻塞队列的实现包含三种:无界队列、有界队列以及同步移交队列。

threadFactory参数用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字,方便定位问题。

handler参数定义了线程池饱和策略。当有界队列被填满后,并且线程池活动线程达到最大线程数,饱和策略开始生效。

ScheduledThreadPoolExecutor

一个可定时调度任务的线程池。

Executors

从上一节内容看出,ThreadPoolExecutor的新建需要传入很多参数,使用起来极不方便。为了便于使用,Executors为我们提供了几个静态工厂方法,大大简化了线程池的创建,它们分别是:

  • newFixedThreadPool:newFixedThreadPool将创建一个固定大小的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化;
  • newCachedThreadPool:newCachedThreadPool将创建一个可缓存的线程池,如果线程池的当前规模超过了处理需求,那么将回收空闲线程;而当需求增加时,可以添加新的线程,线程池的规模不存在任何限制。
  • newSingleThreadExecutor:newSingleThreadExecutor是一个单线程的Executor,它创建单个工作者线程执行任务,如果这个线程异常结束,会创建另一个线程代替。
  • newScheduledThreadPool:创建一个可延迟执行或定期执行的线程池

参考:

相关文章

网友评论

    本文标题:java并发 —— Runnable、Callable、Futu

    本文链接:https://www.haomeiwen.com/subject/ptsvkxtx.html