Java多线程及Future用法

Java多线程及Future用法

同步和异步 – 比要发射10枚导弹,同步的方式就是上一枚导弹炸毁后才发射下一枚,而异步就是全部挨个发射出去,而不在乎它们是否击中目标,这种异步方式也被称为Fire and Forget。Kafka为了提高吞吐性能默认是异步发送消息的。为了更好的了解Kafka的Producer发送,我们先补充一些关于Java多线程的知识。

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。

  • 继承Thread创建线程(不推荐)
  • 实现Runnable接口创建线程
  • 实现Callable接口实现线程
  • 使用线程池Executor创建线程(推荐)

1.继承Thread实现线程

  我们先来看一下Thread的源码,它是一个类,同样也实现了Runnable接口

通过继承Thread类来创建并启动多线程的一般步骤如下

  1. 定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
  2. 创建Thread子类的实例,也就是创建了线程对象
  3. 启动线程,即调用线程的start()方法

代码示例:

2.实现Runnable接口创建线程

  我们来看一下Runnable的源码,它是一个接口:

由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。

通过实现Runnable接口创建并启动线程一般步骤如下:

  1. 定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
  2. 创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
  3. 第三部依然是通过调用线程对象的start()方法来启动线程

代码示例:

3.实现callable接口实现线程

  我们来看一下callable源码,它是一个接口:

它和Runnable接口不一样的是,call()方法提供了2个额外功能:

  • call()方法可以有返回值
  • call()方法可以声明抛出异常

java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

那么怎么使用Callable呢?一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:

第一个submit方法里面的参数类型就是Callable。

暂时只需要知道Callable一般是和ExecutorService配合来使用的,具体的使用方法讲在后面讲述。

一般情况下我们使用第一个submit方法和第三个submit方法,第二个submit方法很少使用。

3.1 Future

我们来看一下Future的源码,它是一个接口,用来返回子线程的计算结果:

我们来看一下它的各个方法:

  • boolean cancel(boolean mayInterruptIfRunning):用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
  • boolean isCancelled():如果在Callable任务正常完成前被取消,返回True
  • boolean isDone():若Callable任务完成,返回True
  • V get() throws InterruptedException, ExecutionException:返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
  • V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException:用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null

因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask

3.2 FutureTask

我们先来看一下FutureTask的实现:

FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

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

FutureTask提供了2个构造器:

事实上,FutureTask是Future接口的一个唯一实现类。

3.3 使用FutureTask对象作为Thread对象的target创建并启动线程

接下来我们看如何创建并启动有返回值的线程:

  1. 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
  3. 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

代码示例:

3.4 使用executor创建线程

3.4.1.使用Callable+Future获取执行结果

代码示例:

3.4.2.使用Callable+FutureTask获取执行结果

4.使用线程池Executor创建线程

4.1 Executor执行Runnable

执行结果:

复制代码

4.2Executor执行Callable

执行结果:

Views: 28