一、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開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/244539.html
標籤:Java
