APP下载

理清Thread、Runable、Callable、FutureTask之间千丝万缕的联络

消息来源:baojiabao.com 作者: 发布时间:2024-05-08

报价宝综合消息理清Thread、Runable、Callable、FutureTask之间千丝万缕的联络

连结:http://www.17coding.info/article/23

一、Thread与Runable

1、建立执行绪的两种方法

在java中你怎么建立执行绪?相信你很快能够想到继承Thread类和实现Runable界面这两种方式。没错,java提供了这两种方式来建立新的执行绪。网上也有各种文章介绍这两种方式建立执行绪的区别,但是我们这里要讲的是这两种方式的关联。先分别看看这两种方式的程式码

1、继承Thread类,重写run方法

public class MyThread extends Thread {

@Override

public void run() {

System.out.println("我是继承Thread类建立的执行绪哟");

}

public static void main(String[] args) {

MyThread myThread = new MyThread();

myThread.start();

}

}

2、实现Runable界面,实现run方法

public class MyRunnable implements Runnable {

@Override

public void run() {

System.out.println("我是实现Runable界面建立的执行绪哟");

}

public static void main(String[] args) {

MyRunnable myRunnable = new MyRunnable();

new Thread(myRunnable).start();

}

}

通过上面的程式码我们不难发现,第一种方式中,继承了Thread类的子类通过重写父类的run方法就可以实现执行绪的建立。而第二种方式中实现Runnable界面的类的物件可以作为一个引数传递到建立的thread物件中。那么Runable为何方神圣?跟Thread类之间又有哪些不为人知的秘密?我们下期………我们下面揭晓。

2、Thread与Runable的关联

我们先看下Runnable的源代码:

public interface Runnable {

public abstract void run();

}

What? Runnable界面这么简单?就一个run方法?是的,你没有看错,Runnable界面就这么简单。如果没有Thread类,那么Runnable界面就是普通得不能再普通的一个界面了,我们在程式码中实现这个界面,也做不了任何事情!但由于得到Thread类的“青睐”,这个界面就变得不一般了!

那么Runable界面得到Thread类的青睐,具体表现在哪呢?我们不妨先看看Thread类的定义

public class Thread implements Runnable{}

原来Thread是Runable的一个实现类!!!以程式人的第一直觉,那Thread自然应该实现了run方法,我们继续在源代码中搜寻!

@Override

public void run() {

if (target != null) {

target.run();

}

}

果不其然,Thread实现了run方法,并且有个判断,当target为null的时候什么也不做,否则执行target的run方法,target又是什么呢?

private Runnable target;

target也是一个Runable物件,那这个私有的字段在哪里赋值的呢?我们继续寻找发现是在init方法里面进行赋值的,并且最终在建构函式中呼叫了init方法,我们看看建构函式的定义(Thread过载了多个建构函式)

public Thread(Runnable target) {

init(null, target, "Thread-" + nextThreadNum(), 0);

}

这里我们能看到Thread的建构函式支援Runable的引数,不知道到目前大家有没有捋清楚Thread与Runable的关系,我们再总结一下:

1、 Runable原本平民,只是一个普通的界面。

2、Thread实现了Runable界面,并且实现了界面的run方法。

3、Thread提供了过载的建构函式,接收Runable型别的引数。在Thread重写的run方法中对呼叫了建构函式传入的Runable实现类的run方法。

所以不管我们用哪种方式建立执行绪,都要实现或者重写run方法!并且这个run方法都是实现的Runable界面中的方法。这个run方法就是我们自定义的需要线上程中处理的一些逻辑!那这个run方法在哪里呼叫的呢?直接呼叫run方法可以建立新的执行绪么?为什么我们在程式码中都是呼叫的start方法去启动一个执行绪?start方法与run方法有什么关联?

好奇心的驱使,我再次打开了源代码中的start方法一探究竟,发现这个方法中最主要的行为是呼叫了一个名为start0的方法。

public synchronized void start() {

……

start0();

……

}

那start0又是什么呢?

private native void start0();

看到native关键字我们就应该知道,这个方法是JVM的方法,具体的实现需要检视C的程式码!

3、start方法与run方法的关联

鉴于自己C语言很烂,且多年没有碰过了,但是又想弄清楚start方法与run方法的关联,于是在网上查找了相关的资料,下面做一下整理。参考资料:https://www.ibm.com/developerworks/cn/java/j-lo-processthread/

在Thread 类的顶部,有个native的registerNatives本地方法,该方法主要的作用就是注册一些本地方法供 Thread 类使用,如start0(),stop0() 等等,可以说,所有操作本地执行绪的本地方法都是由它注册的 . 这个方法放在一个static语句块中,这就表明,当该类被载入到JVM中的时候,它就会被呼叫,进而注册相应的本地方法。

private static native void registerNatives();

static {

registerNatives();

}

本地方法registerNatives是定义在Thread.c档案中的。Thread.c是个很小的档案,定义了各个操作系统平台都要用到的关于执行绪的公用资料和操作

JNIEXPORT void JNICALL

Java_Java_lang_Thread_registerNatives (JNIEnv *env, jclass cls){

(*env)->RegisterNatives(env, cls, methods, ARRAY_LENGTH(methods));

}

static JNINativeMethod methods[] = {

……

{"start0", "()V",(void *)&JVM_StartThread},

{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},

……

};

到此,可以容易的看出Java执行绪呼叫start的方法,实际上会呼叫到JVM_StartThread方法,那这个方法又是怎样的逻辑呢。实际上,我们需要的是(或者说 Java 表现行为)该方法最终要呼叫Java执行绪的run方法,事实的确如此。 在jvm.cpp中,有如下程式码段:

JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))

……

native_thread = new JavaThread(&thread_entry, sz);

……

这里JVM_ENTRY是一个宏,用来定义JVM_StartThread 函式,可以看到函式内建立了真正的平台相关的本地执行绪,其执行绪函式是 thread_entry,程式码如下所示。

static void thread_entry(JavaThread* thread, TRAPS) {

HandleMark hm(THREAD);

Handle obj(THREAD, thread->threadObj());

JavaValue result(T_VOID);

JavaCalls::call_virtual(&result,obj,

KlassHandle(THREAD,SystemDictionary::Thread_klass()),

vmSymbolHandles::run_method_name(),

vmSymbolHandles::void_method_signature(),THREAD);

}

可以看到呼叫了vmSymbolHandles::run_method_name方法,这是在 vmSymbols.hpp用宏定义的:

class vmSymbolHandles: AllStatic {

template(run_method_name,"run")

}

至于run_method_name是如何宣告定义的,因为涉及到很繁琐的程式码细节,本文不做赘述。感兴趣的读者可以自行检视JVM的源代码。

综上所述,Java执行绪的建立呼叫过程如上图所示,首先 , Java执行绪的start方法会建立一个本地执行绪(通过呼叫JVM_StartThread),该执行绪的执行绪函式是定义在jvm.cpp中的thread_entry,由其再进一步呼叫run方法。可以看到Java执行绪的run方法和普通方法其实没有本质区别,直接呼叫run方法不会报错,但是却是在当前执行绪执行,而不会建立一个新的执行绪。

二、FutureTask、Callable与Runable

1、建立能获取结果的异步执行绪

上面我们了解了建立执行绪建立的两种方式,但是我们也能看到,通过runable方式建立的执行绪无法获取返回值,如果我们需要异步执行某个操作,并且得到返回的结果,可能上面的两种建立执行绪的方式就不适用啦(也不是说不可以,比如通过共享变数等方式,但是使用起来会比较麻烦)!在java中提供一种比较方便的方式,那就是使用FutureTask类,我们先看看使用方式:

public class MyFutrueTask implements Callable {

@Override

public String call() throws Exception {

System.out.println("我是Future模式的执行绪啦");

return "Future模式的执行绪结束啦";

}

public static void main(String[] args) throws TimeoutException, ExecutionException, InterruptedException {

FutureTask futureTask = new FutureTask(new MyFutrueTask());

new Thread(futureTask).start();

String result = futureTask.get(2000, TimeUnit.MILLISECONDS);

System.out.println(result);

}

}

主类实现了一个名为Callable的界面,并且实现了界面的call方法,具体的业务逻辑就在call方法中实现!实现Callable界面的类的物件可以作为一个引数传递到建立的FutureTask物件的建构函式中,而FutureTask类的物件又作为一个引数传递到Thread物件的建构函式中……根据上面我们对Thread和Runable的了解,能够作为引数传入到Thread建构函式的物件,一定是实现了Runable界面的!那是不是说FutureTask物件就应该是一个Runable的实现类呢?

2、FutureTask实现

我们先看看FutureTask类的定义

public class FutureTask implements RunnableFuture

FutureTask是一个泛型类,并且实现了RunableFutrue泛型界面,我们继续跟进

public interface RunnableFuture extends Runnable, Future {

void run();

}

RunableFutrue界面继承了Runable界面,这也就是说FutureTask间接的实现了Runable界面,FutureTask也是Runable的一个实现类,那也就必须要实现run方法,还能够将例项物件传入Thread物件的构造后函式!RunableFutrue界面还继承了另外的一个名为Futrue的泛型界面,我们看看该界面的定义

public interface Future {

boolean cancel(boolean mayInterruptIfRunning);

boolean isCancelled();

boolean isDone();

V get();

V get(long timeout, TimeUnit unit);

}

根据这些方法的命名能够看出来,FutureTask实现了Future界面后,就应该拥有了取消执行绪、判断执行绪执行状态、获取结果等功能!我们整体看一下FutureTask的类图:

FutureTask类中对这些界面的具体实现是怎么样的呢?我们可以到FutureTask类中一探究竟,先瞅瞅建构函式

public FutureTask(Callable callable) {

if (callable == null)

throw new NullPointerException();

this.callable = callable;

this.state = NEW;

}

建构函式接收一个Callable型别的引数,Callable是一个泛型型别的界面,该界面只有一个名为call的方法。本来这也只是一个普通的界面,但由于收到FutrueTask的“青睐”,这个界面变得不一般了!

public interface Callable {

V call() throws Exception;

}

乍一看是不是觉得跟Runable界面很像呢?但是Callable界面的call方法有返回值!Callable、Runable、FutureTask之间是怎么关联起来的呢?我们上面有说了FutureTask是Runable的一个实现类,那FutureTask是不是应该也实现了run方法呢?我们跟进一下程式码:

public void run() {

......

try {

Callable c = callable;

if (c != null && state == NEW) {

V result;

boolean ran;

try {

result = c.call();

ran = true;

} catch (Throwable ex) {

......

}

if (ran)

set(result);

}

} finally {

......

}

}

Run方法的实现也比较简单,上述程式码只保留了需要关注的程式码。在run方法中呼叫了建构函式传入的Callable实现类的call方法,并且用result的变数接收方法的返回值,最后呼叫set方法将返回结果设定到类的属性,由于FutureTask实现了Future界面,所以也就有了获取返回值以及判断执行绪是否执行完成、取消的能力!也就是说,我们自定义Callable实现类的call方法,最终会在FutureTask类(也就是Runable)的run方法中执行!!!

2020-01-09 06:49:00

相关文章