一、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开发手册(嵩山版)》最新公布,速速下载!
感觉不错,别忘了顺手点赞+转发哦!