源码地址:https://github.com/nieandsun/concurrent-study.git
1 创建一个线程的方式到底有几种???在java中其实创建一个线程的方式本质上只有两种:
(1)继承Thread类 (2)实现Runnable接口这个观点可以通过Thread类中的注释进行证明:
但是同时相信很多人也都知道,使用下面两种组合,也可以创建一个线程:
这时我想有些人可能会懵逼,难道JDK源码中的注释还有问题??? —> 其实并不是。
2 如何使用FutureTask 、Future、Callable、线程池实现线程 2.1 FutureTask + Callable实现多线程 codepackage com.nrsc.ch2.future;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
@Slf4j
public class FutureTaskDemo1 {
static class MyCallable implements Callable {
@Override
public Integer call() throws Exception {
int num = 0;
log.info("开始进行计算。。。");
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
num++;
}
return num;
}
}
public static void main(String[] args) throws Exception {
FutureTask futureTask = new FutureTask(new MyCallable());
new Thread(futureTask).start(); //-----//
Thread.sleep(1000);
log.info("do something in main");
//在Callable中的方法运行完之前 这里会一直阻塞,直到Callable运行完
//然后就可以从future里获取到结果了
Integer result = futureTask.get();
log.info("result: {}", result);
}
}
运行结果
2.2 线程池+Future+Callable 实现多线程
code
package com.nrsc.ch2.future;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@Slf4j
public class FutureDemo1 {
static class MyCallable implements Callable {
@Override
public Integer call() throws Exception {
int num = 0;
log.info("开始进行计算。。。");
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
num++;
}
return num;
}
}
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
//线程池通过submit开启线程
Future future = executorService.submit(new MyCallable());
Thread.sleep(1000);
log.info("do something in main");
//在Callable中的方法运行完之前 这里会一直阻塞,直到Callable运行完
//然后就可以从future里获取到结果了
Integer result = future.get();
log.info("result: {}", result);
executorService.shutdown();
}
}
运行结果,和2.1基本一致:
3 Runnable、Callable、Future和FutureTask之间的关系
3.1 整体关系介绍
看过2中的两个栗子,我想你肯定也已经知道揭开这几个对象之间关系的突破口就是FutureTask
。
为什么这么说呢? 首先我们应该知道Thread的构造函数就下面这么几个。并且在1中也已经说过,Java中开启线程的方式要么是继承Thread类,要么是使用继承了Runnable接口的类。但是在2.1中我却在创建Thread类时传入了一个FutureTask ----》 那这说明这个FutureTask 肯定就是一个Runnable(多态)。
接下来我们来看一下FutureTask类的继承关系图:
由此可知FutureTask确实是Runnable接口的子类,同时它还实现了Future接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
RunnableFuture接口源码:
public interface RunnableFuture extends Runnable, Future {
void run();
}
Future接口:
public interface Future {
//尝试取消线程
boolean cancel(boolean mayInterruptIfRunning);
//判断线程任务是否已经被取消
boolean isCancelled();
//判断线程中的任务是否执行完
boolean isDone();
//获取执行任务的结果 ---》 在任务执行完之前一直阻塞
V get() throws InterruptedException, ExecutionException;
//在指定时间内尝试获取执行任务的结果
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Callable的源码:
@FunctionalInterface
public interface Callable {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
接着再来看一下FutureTask中的方法
通过前面的内容可知:
(1)JDK允许的创建线程的方式只有两种,而这两种方式都无法获取线程中任务运行结果的返回值 (2)为了想办法获取到各个线程的执行结果,在没办法改变创建线程方式的前提下 (2.1)出现了FutureTask,一种特殊的Runnable (2.2)我们的线程开启后其实还是会走没有返回值的run()方法 —》 其实就是实现Runnable接口开启线程的那种方式 (2.3)但是在没有返回值的run()方法内部,可以调用因实现了Callable接口而重写的有返回值的call()方法 (2.4)在call()方法调用完之后,就可以获取到该线程的执行结果了 (3)Future的作用之一就是拿到线程的执行结果。画个图的话,可以这样表示:
介绍到这里之后,有兴趣的可以看一看为什么使用线程池,传入一个Callable,返回的是一个Future。。。相信你肯定能看出个123来☺☺☺
end