源码网商城,靠谱的源码在线交易网站 我的订单 购物车 帮助

源码网商城

Java线程的start方法回调run方法的操作技巧

  • 时间:2022-05-18 11:21 编辑: 来源: 阅读:
  • 扫一扫,手机访问
摘要:Java线程的start方法回调run方法的操作技巧
面试中可能会被问到为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法? [b]Java 创建线程的方法[/b] 实际上,创建线程最重要的是提供线程函数(回调函数),该函数作为新创建线程的入口函数,实现自己想要的功能。Java 提供了两种方法来创建一个线程: [b]继承 Thread 类[/b]
class MyThread extends Thread{ 
 public void run() { 
  System.out.println("My thread is started."); 
 } 
}
实现该继承类的 run 方法,然后就可以创建这个子类的对象,调用 start 方法即可创建一个新的线程:
MyThread myThread = new MyThread(); 
myThread.start();
[b]实现 Runnable 接口[/b]
class MyRunnable implements Runnable{ 
 public void run() { 
  System.out.println("My runnable is invoked."); 
 } 
}
实现 Runnable 接口的类的对象可以作为一个参数传递到创建的 Thread 对象中,同样调用 Thread#start 方法就可以在一个新的线程中运行 run 方法中的代码了。
Thread myThread = new Thread( new MyRunnable()); 
myThread.start();
可以看到,不管是用哪种方法,实际上都是要实现一个 run 方法的。 该方法本质是上一个回调方法。由 start 方法新创建的线程会调用这个方法从而执行需要的代码。 从后面可以看到,run 方法并不是真正的线程函数,只是被线程函数调用的一个 Java 方法而已,和其他的 Java 方法没有什么本质的不同。 [b]Java 线程的实现[/b] 从概念上来说,一个 Java 线程的创建根本上就对应了一个本地线程(native thread)的创建,两者是一一对应的。 问题是,本地线程执行的应该是本地代码,而 Java 线程提供的线程函数是 Java 方法,编译出的是 Java 字节码,所以可以想象的是, Java 线程其实提供了一个统一的线程函数,该线程函数通过 Java 虚拟机调用 Java 线程方法 , 这是通过 Java 本地方法调用来实现的。 以下是 Thread#start 方法的示例:
public synchronized void start() { 
 …
 start0(); 
 …
}
可以看到它实际上调用了本地方法 start0, 该方法的声明如下:
private native void start0();
Thread 类有个 registerNatives 本地方法,该方法主要的作用就是注册一些本地方法供 Thread 类使用,如 start0(),stop0() 等等,可以说,所有操作本地线程的本地方法都是由它注册的 . 这个方法放在一个 static 语句块中,这就表明,当该类被加载到 JVM 中的时候,它就会被调用,进而注册相应的本地方法。
private static native void registerNatives(); 
static{ 
 registerNatives(); 
}
本地方法 registerNatives 是定义在 Thread.c 文件中的。Thread.c 是个很小的文件,定义了各个操作系统平台都要用到的关于线程的公用数据和操作,如代码清单 1 所示。 清单1
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}, 
 {"isAlive","()Z",(void *)&JVM_IsThreadAlive}, 
 {"suspend0","()V",(void *)&JVM_SuspendThread}, 
 {"resume0","()V",(void *)&JVM_ResumeThread}, 
 {"setPriority0","(I)V",(void *)&JVM_SetThreadPriority}, 
 {"yield", "()V",(void *)&JVM_Yield}, 
 {"sleep","(J)V",(void *)&JVM_Sleep}, 
 {"currentThread","()" THD,(void *)&JVM_CurrentThread}, 
 {"countStackFrames","()I",(void *)&JVM_CountStackFrames}, 
 {"interrupt0","()V",(void *)&JVM_Interrupt}, 
 {"isInterrupted","(Z)Z",(void *)&JVM_IsInterrupted}, 
 {"holdsLock","(" OBJ ")Z",(void *)&JVM_HoldsLock}, 
 {"getThreads","()[" THD,(void *)&JVM_GetAllThreads}, 
 {"dumpThreads","([" THD ")[[" STE, (void *)&JVM_DumpThreads}, 
};
到此,可以容易的看出 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,如清单 2 所示。 清单2
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 线程创建调用关系图 [img]http://files.jb51.net/file_images/article/201711/201711100954301.png[/img] start() 创建新进程 run() 没有 [b]PS:下面看下Java线程中run和start方法的区别[/b] Thread类中run()和start()方法的区别如下: run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用; start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;
package com.ljq.test;
public class ThreadTest {
  /**
   * 观察直接调用run()和用start()启动一个线程的差别 
   * 
   * @param args
   * @throws Exception
   */
  public static void main(String[] args){
    Thread thread=new ThreadDemo();
    //第一种
    //表明: run()和其他方法的调用没任何不同,main方法按顺序执行了它,并打印出最后一句
    //thread.run();
    //第二种
    //表明: start()方法重新创建了一个线程,在main方法执行结束后,由于start()方法创建的线程没有运行结束,
    //因此主线程未能退出,直到线程thread也执行完毕.这里要注意,默认创建的线程是用户线程(非守护线程)
    //thread.start();
    //第三种
    //1、为什么没有打印出100句呢?因为我们将thread线程设置为了daemon(守护)线程,程序中只有守护线程存在的时候,是可以退出的,所以只打印了七句便退出了
    //2、当java虚拟机中有守护线程在运行的时候,java虚拟机会关闭。当所有常规线程运行完毕以后,
    //守护线程不管运行到哪里,虚拟机都会退出运行。所以你的守护线程最好不要写一些会影响程序的业务逻辑。否则无法预料程序到底会出现什么问题
    //thread.setDaemon(true);
    //thread.start();
    //第四种
    //用户线程可以被System.exit(0)强制kill掉,所以也只打印出七句
    thread.start();
    System.out.println("main thread is over");
    System.exit(1);
  }
  public static class ThreadDemo extends Thread{
    @Override
    public void run() {
      for (int i = 0; i < 100; i++) {
        System.out.println("This is a Thread test"+i);
      }
    }
  }
}
[b]总结[/b] 以上所述是小编给大家介绍的Java线程的start方法回调run方法的操作技巧,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程素材网网站的支持!
  • 全部评论(0)
联系客服
客服电话:
400-000-3129
微信版

扫一扫进微信版
返回顶部