我们都知道实现线程的方式有三种,分别是继承Thread类、实现Runnable接口和实现Callable接口。大致说一下使用场景:
- 继承Thread类:如果类已经继承了其他接口,就不能通过此种方式实现线程了
- 实现Runnable接口:不需要返回结果的线程
- 实现Callable接口:需要返回结果
线程返回值是通过FutureTask拿到的结果,下面是FutureTask类的继承关系:
下面通过一个例子说明一下怎么获取线程的执行结果:
//定义一个类,实现Callable接口
public class MyCallable implements Callable { @Override public Object call() throws Exception {
//睡眠5秒中,模拟任务的执行 TimeUnit.SECONDS.sleep(5);
//返回的结果值 return "5s OK"; } }
public class Main { public static void main(String[] args) throws Exception{ System.out.println("----------main start----------"); //通过构造方法传入实现Callable接口的实例 //构造方法也可以传入实现Runnable接口的实例,最终还是适配器转换成了Callable FutureTask<String> futureTask = new FutureTask<String>(new MyCallable()); //FutureTask最终还是一个实现了Runnable接口的类,最终还是需要开启一个线程去执行任务 new Thread(futureTask).start(); //get是阻塞方法,取到值之前会一直阻塞 String value = futureTask.get(); System.out.println("thread result:" + value); System.out.println("----------main end----------"); } }
最终的执行结果:
源码分析
//Thread中的start方法
public synchronized void start() { //判断线程状态,不是新建状态就抛出异常 if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; //调用本地方法,开启一个线程 start0(); started = true; } //本地方法 private native void start0();
从源码可以看出调用Thread.start()方法后会调用本地的start0()方法开启线程,具体执行逻辑是在run()方法中。
//Thread中的run方法调用了实现Runnable接口的run()方法
@Override public void run() { if (target != null) { target.run(); } }
从类图中可以看到FutureTask是实现了Runnable接口的类,所以Thread中的run()方法最终执行的是FutureTask中的run()方法,下面是源码:
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 {
//调用Callable中的call方法 result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result); } } finally { runner = null; int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
其实FutureTask还是比较简单,Callable就是他的任务,而FutureTask内部维护了一个任务状态,所有的状态都是围绕这个任务来进行的,随着任务的进行,状态也在不断的更新。任务发起者调用get()方法时,如果任务没有执行完成,会将当前线程放入阻塞队列等待,当任务执行完后,会唤醒阻塞队列中的线程。