之前学习并发编程的时候,对这几个接口和类就比较模糊。在单点学习完之后,还应该拿出来进行比较、找出关联关系,这样才能有更深的理解。✊加油!
Thread类和Runnable接口
创建线程的方式
多线程里有个经典的面试题:创建线程有哪几种方式?当时硬背下来的方式:
- 自定义类继承Thread类,并重写run方法,直接new该子类,并启动线程。
- 自定义类实现Runnable接口,并重写run方法。将该子类对象传给new Thread构造方法,并启动线程。
- 创建线程池,向线程池提交Runnable任务、Callable任务、FutureTask任务。
(但如果再深入问一下,肯定就说不上来了😂)
创建线程的第1、2两种方式,代码分别如下:
自定义类继承Thread,并重写run方法
java
public class PrintStoryExtends extends Thread{
String text;
long interval;
public PrintStoryExtends(String text, long interval){
this.text = text;
this.interval = interval;
}
public void run(){
try{
System.out.println("执行这段代码的线程名字是:" + Thread.currentThread().getName());
printStory(text, interval);
System.out.println(Thread.currentThread().getName() + "执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
protected void printStory(String text, long interval) throws InterruptedException {
for (char ch : text.toCharArray()){
Thread.sleep(interval);
System.out.print(ch);
}
System.out.println("----");
}
}
//main函数所在类
public class PrintStoryAppMain {
public static final String text = "今天又是阳光明媚的一天,某小胖7点钟起床洗漱完成,奔向地铁站去往浦东图书馆,在龙阳路换乘时,发现今天有好多人啊," +
"还都是背着小书包的,某小胖心里想:从地铁站就开始卷了嘛,等下一下车要奔跑哇。";
public static void main(String[] args) {
System.out.println("程序执行开始,执行线程的名字叫做:" + Thread.currentThread().getName());
for (int i = 1; i <= 2; i++){
Thread thread = new PrintStoryExtends(text, 200 * i);
thread.start();;
}
System.out.println("启动线程结束,名字叫做:" + Thread.currentThread().getName());
}
}
在main方法中new PrintStoryExtends之后,调用start方法,会创建一个线程(一定要调用start方法,才会创建线程,如果直接调用run方法,只是用主线程去执行了普通的run方法),该线程执行run方法中的业务逻辑。
自定义类实现Runnable接口,并重写run方法。new Thread的时候,传入该自定义类
java
public class PrintStoryImplements implements Runnable{
private String text;
private long interval;
public PrintStoryImplements(String text, long interval){
this.text = text;
this.interval = interval;
}
@Override
public void run() {
try{
System.out.println("执行这段代码的线程名字叫做" + Thread.currentThread().getName());
printStory(text, interval);
System.out.println(Thread.currentThread().getName() + "执行结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void printStory(String text, long interval) throws InterruptedException{
for (char ch : text.toCharArray()){
Thread.sleep(interval);
System.out.print(ch);
}
System.out.println();
}
}
// main方法,创建并启动线程,执行
public class PrintStoryAppMain {
public static final String text = "今天又是阳光明媚的一天,某小胖7点钟起床洗漱完成,奔向地铁站去往浦东图书馆,在龙阳路换乘时,发现今天有好多人啊," +
"还都是背着小书包的,某小胖心里想:从地铁站就开始卷了嘛,等下一下车要奔跑哇。";
public static void main(String[] args) {
System.out.println("程序执行开始,执行线程的名字叫做:" + Thread.currentThread().getName());
for (int i = 1; i <= 2; i++){
Thread thread = new Thread(new PrintStoryImplements(text, 200 * i), "我的线程-" + i);
thread.start();;
}
System.out.println("启动线程结束,名字叫做:" + Thread.currentThread().getName());
}
}
为什么new Thread的时候可以传入实现了Runnable接口的对象?
Thread类和Runnable接口的关联
Runnable源码,非常简单
java
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
Thread源码
java
public class Thread implements Runnable {
// 构造方法,传入Runnable对象和线程的名称,调用init方法进行线程的初始化
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
// run方法,重写了Runnable接口的run方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
根据源码,可以很明显的知道:
- Thread类实现了Runnable接口,并重写了run方法。
- Thread类的构造函数支持传入Runnable类型的对象(即实现了Runnable接口的子类),并调用init方法初始化线程
- 如果new Thread的时候传入了Runnable类型的对象(target),则执行Thread类的run方法的时候,会去执行target的run方法(也就是实现了Runnable接口的子类的run方法,这里面写了具体的业务逻辑。如上述例子中PrintStoryImplements类的run方法。)
Callable接口、Runnable接口
Callable接口源码
上述两种创建线程的方式,都无法获取到线程执行之后的返回结果(Runnable接口的run方法返回值是void),所以有了Callable接口。
Callable接口的源码
java
@FunctionalInterface
public interface Callable<V> {
/**
* 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;
}
这是一个泛型接口,call方法有返回值,如果不能输出返回值,也会抛出异常,可以清楚知道线程的执行情况。
Callable接口如何使用
创建线程的时候,Thread的构造方法可以传入Runnable对象,但是没有构造方法可以直接传入Callable对象。
Callable接口通常和FutureTask类结合使用。FutureTask类实现了RunnableFuture接口,该接口继承自Runnable和Future接口(详见后续小节)。
使用创建Callable类型的线程步骤:
- 自定义类实现Callable接口
- 将上述自定义类传入FutureTask的构造函数中,初始化一个FutureTask对象
- 将上述futureTask对象传入new Thread方法创建线程(FutureTask间接实现了Runnable接口,所以可以传入Thread的构造函数)
- 线程调用start方法进行启动
- 可通过futureTask对象的get方法获取上述线程的执行结果
代码如下
java
public class MyCallableTask implements Callable<String> {
public String call() throws Exception {
System.out.println("我是实现了Callable接口的线程");
return "Callable线程结束";
}
}
public class MyCallableTaskMain {
public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
FutureTask<String> futureTask = new FutureTask<>(new MyCallableTask());
Thread thread = new Thread(futureTask);
thread.start();
//获取线程执行结果
String result = futureTask.get(2000, TimeUnit.MILLISECONDS);
System.out.println(result);
}
}
Future、RunnableFuture、FutureTask
由FutureTask向上追溯。
FutureTask类
FutureTask类源码
java
public class FutureTask<V> implements RunnableFuture<V> {
//构造方法,可传入Callable对象
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
// 可传入Runnable对象
// Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务。
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
//以下几个方法,都是Future接口中的方法
public boolean isCancelled() {
return state >= CANCELLED;
}
public boolean isDone() {
return state != NEW;
}
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
/**
* @throws CancellationException {@inheritDoc}
*/
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
//重写的RunnableFuture接口的方法,即Runnable接口的方法
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
}
RunnableFuture接口源码
源码
csharp
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
总结
- FutureTask间接实现了Runnable接口(因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行)、Future接口。
- FutureTask重写了Runnable接口的run方法,核心逻辑是:。。。
- FutureTask重写了Future接口的几个对线程执行结果进行处理的方法。所以可以直接通过get()函数获取执行结果,该函数会阻塞,直到结果返回。
- FutureTask既是Future、Runnable,又包装了Callable(如果是Runnable最终也会被转换为Callable ),它是这两者的合体。
Executor就是Runnable和Callable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行取消 、 查询是否完成 、获取结果 、设置结果操作。
FutureTask
相当于对Callable
进行了封装,管理Callable
任务执行的情况,存储了 Callable
的 call
方法的任务执行结果。
Future模式
Future是Java5新加的一个接口,它提供了一种异步并行计算的功能。如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。主线程继续处理其他任务,处理完成后,再通过Future获取计算结果。
这其实就是多线程中经典的 Future 模式,你可以将其看作是一种设计模式,核心思想是异步调用,主要用在多线程领域,并非 Java 语言独有。
Future接口
Future接口是一个泛型接口,位于 java.util.concurrent
包下,其中定义了 5 个方法,主要包括下面这 4 个功能:
- 取消任务;
- 判断任务是否被取消;
- 判断任务是否已经执行完成;
- 获取任务执行结果。
java
// V是Future执行任务的任务返回值类型
public interface Future<V> {
// 取消任务执行。成功返回true,否则返回false;
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否被取消
boolean isCancelled();
// 判断任务是否已完成
boolean isDone();
// 获取任务执行结果
V get() throws InterruptedException, ExecutionException;
//获取任务执行结果。若指定时间内没有返回计算结果,则抛出抛出TimeoutException异常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
Callable和Future的关系
- 两者是通过FutureTask关联的
- FutureTask实现了Runnable接口和Future接口
- FutureTask的构造函数可以传入Callable对象,相当于对
Callable
进行了封装,管理Callable
任务执行的情况,存储了Callable
的call
方法的任务执行结果。