zoukankan      html  css  js  c++  java
  • Thread, Runable, Callable 还傻傻分不清?

    一、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方法,我们继续在源码中搜寻!

    @Override
    public 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/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与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开发手册(嵩山版)》最新发布,速速下载!

    觉得不错,别忘了随手点赞+转发哦!

  • 相关阅读:
    mysql的触发器
    数据库面试题
    数据库面试(1)
    eclipse里maven项目An error occurred while filtering resources解决办法
    Missing artifact com.github.pagehelper:pagehelper:jar:3.4.2-fix的解决方法
    淘淘商城学习
    spring Security简介
    ElasticSearch学习
    在Visual Studio中使用FFTW库
    FFTW程序Demo
  • 原文地址:https://www.cnblogs.com/javastack/p/14230103.html
Copyright © 2011-2022 走看看