一、Thread与Runnable

1、创立线程的两种办法

在java中你怎么创立线程?置信你很快可能想到继承Thread类和实现Runnable接口这两种形式。

没错,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、实现Runnable接口,实现run办法

public class MyRunnable implements Runnable {            @Override    public void run() {        System.out.println("我是实现Runnable接口创立的线程哟");    }            public static void main(String[] args) {        MyRunnable myRunnable = new MyRunnable();                new Thread(myRunnable).start();    }}

   通过下面的代码咱们不难发现,第一种形式中,继承了Thread类的子类通过重写父类的run办法就能够实现线程的创立。

而第二种形式中实现Runnable接口的类的对象能够作为一个参数传递到创立的thread对象中。那么Runnable为何方神圣?跟Thread类之间又有哪些鲜为人知的机密?咱们下期………咱们上面揭晓。

2、Thread与Runnable的关联

   咱们先看下Runnable的源码:

public interface Runnable {      public abstract void run();}

What? Runnable接口这么简略?就一个run办法?

是的,你没有看错,Runnable接口就这么简略。如果没有Thread类,那么Runnable接口就是一般得不能再一般的一个接口了,咱们在代码中实现这个接口,也做不了任何事件!但因为失去Thread类的“青眼”,这个接口就变得不个别了!

那么Runnable接口失去Thread类的青眼,具体表现在哪呢?咱们无妨先看看Thread类的定义

public class Thread implements Runnable{}

   原来Thread是Runnable的一个实现类!!!以程序人的第一直觉,那Thread天然应该实现了run办法,咱们持续在源码中搜查!

@Overridepublic void run() {          if (target != null) {        target.run();  }}

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

private Runnable target;

target也是一个Runnable对象,那这个公有的字段在哪里赋值的呢?咱们持续寻找发现是在init办法外面进行赋值的,并且最终在构造函数中调用了init办法,咱们看看构造函数的定义(Thread重载了多个构造函数)

public Thread(Runnable target) {    init(null, target, "Thread-" + nextThreadNum(), 0);}

这里咱们能看到Thread的构造函数反对Runnable的参数,不晓得到目前大家有没有捋分明Thread与Runnable的关系,咱们再总结一下:

1、 Runnable本来平民,只是一个一般的接口。
2、Thread实现了Runnable接口,并且实现了接口的run办法。
3、Thread提供了重载的构造函数,接管Runnable类型的参数。在Thread重写的run办法中对调用了构造函数传入的Runnable实现类的run办法。

   所以不论咱们用哪种形式创立线程,都要实现或者重写run办法!并且这个run办法都是实现的Runnable接口中的办法。这个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/developer...

在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与Runnable

1、创立能获取后果的异步线程

下面咱们理解了创立线程创立的两种形式,然而咱们也能看到,通过runnable形式创立的线程无奈获取返回值,如果咱们须要异步执行某个操作,并且失去返回的后果,可能下面的两种创立线程的形式就不实用啦(也不是说不能够,比方通过共享变量等形式,然而应用起来会比拟麻烦)!在java中提供一种比拟不便的形式,那就是应用FutureTask类,咱们先看看应用形式:

public class MyFutrueTask implements Callable<String> {    @Override    public String call() throws Exception {        System.out.println("我是Future模式的线程啦");        return "Future模式的线程完结啦";    }    public static void main(String[] args) throws TimeoutException, ExecutionException, InterruptedException {        FutureTask<String> futureTask = new FutureTask<String>(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和Runnable的理解,可能作为参数传入到Thread构造函数的对象,肯定是实现了Runnable接口的!那是不是说FutureTask对象就应该是一个Runnable的实现类呢?

2、FutureTask实现

咱们先看看FutureTask类的定义

public class FutureTask<V> implements RunnableFuture<V>

FutureTask是一个泛型类,并且实现了RunnableFutrue泛型接口,咱们持续跟进

public interface RunnableFuture<V> extends Runnable, Future<V> {       void run();}

RunnableFutrue接口继承了Runnable接口,这也就是说FutureTask间接的实现了Runnable接口,FutureTask也是Runnable的一个实现类,那也就必须要实现run办法,还可能将实例对象传入Thread对象的结构后函数!RunnableFutrue接口还继承了另外的一个名为Futrue的泛型接口,咱们看看该接口的定义

public interface Future<V> {   boolean cancel(boolean mayInterruptIfRunning);   boolean isCancelled();   boolean isDone();   V get();   V get(long timeout, TimeUnit unit);}

依据这些办法的命名可能看进去,FutureTask实现了Future接口后,就应该领有了勾销线程、判断线程运行状态、获取后果等性能!

咱们整体看一下FutureTask的类图:

   FutureTask类中对这些接口的具体实现是怎么样的呢?咱们能够到FutureTask类中一探到底,先瞅瞅构造函数

public interface Future<V> {   boolean cancel(boolean mayInterruptIfRunning);   boolean isCancelled();   boolean isDone();   V get();   V get(long timeout, TimeUnit unit);}

   构造函数接管一个Callable类型的参数,Callable是一个泛型类型的接口,该接口只有一个名为call的办法。原本这也只是一个一般的接口,但因为收到FutrueTask的“青眼”,这个接口变得不个别了!

public interface Callable<V> {      V call() throws Exception;}

   乍一看是不是感觉跟Runnable接口很像呢?然而Callable接口的call办法有返回值!Callable、Runnable、FutureTask之间是怎么关联起来的呢?咱们下面有说了FutureTask是Runnable的一个实现类,那FutureTask是不是应该也实现了run办法呢?咱们跟进一下代码:

public void run() {    ......    try {        Callable<V> 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类(也就是Runnable)的run办法中执行!而Runnable中的run办法,最终又会在Thread类的run办法中执行!!!
近期热文举荐:

1.Java 15 正式公布, 14 个新个性,刷新你的认知!!

2.终于靠开源我的项目弄到 IntelliJ IDEA 激活码了,真香!

3.我用 Java 8 写了一段逻辑,共事直呼看不懂,你试试看。。

4.吊打 Tomcat ,Undertow 性能很炸!!

5.《Java开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!