在java中給我們提供了三種方式來創建多線程。前兩種是我們比較常見的,第三種是JDK1.5之后提供給我們的。接下來我們詳細的看一下這三種創建線程的寫法。
繼承Thread類
第一種方式是繼承Thread類的寫法,代碼如下:
Thread thread = new Thread(){
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("線程創建的第一種方式:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
注意這里我們覆蓋的是run方法,而不是start方法,并且也千萬不要覆蓋start方法。為什么不能覆蓋start方法呢?我們看一下Thread源碼中start方法是怎么寫的:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
注意這個方法是加鎖的方法。在這個方法中最重要的一段代碼是:start0();我們接著再來看一下start0()這個方法:
private native void start0();
它是個native方法。就是這個native的start0()方法,它實現了啟動線程,申請棧內存、運行run方法、修改線程狀態等職責。線程管理和棧內存管理都是由jvm負責的。如果你覆蓋了start方法,也就是撤銷了線程管理和棧內存管理的能力,這樣還如何啟動一個線程呢?不過Thread的這個設計是很精妙的,因為你只需要關注你的業務邏輯就行了,而對于線程和棧內存的管理都有JVM來做就行了。如果在你的開發中不得不要覆蓋start方法的話,請千萬要記得調用super.start(),要不然你的線程無法啟動。
實現Runnable接口
第二種創建線程的方式是實現Runnable接口。具體代碼請看下面:
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("線程創建的第二種方式:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread2.start();
這種寫法是實現了Runnable接口的一種寫法。它的原理是什么呢?我們來看一下Thread源碼中run的寫法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
在run方法中我們可以看到如果target != null就調用target.run()方法。而這個target是從哪兒來的呢?我們繼續看Thread的源碼,發現在init的方法中有這樣一句話:
this.target = target;
接下來我們再看init()這個方法是在哪兒被調用的?通過翻讀源碼我們可以發現在Thread的每一個構造函數中都會調用init這個方法,并且有這樣一個構造函數:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
看到了吧。我們就是在創建Thread的時候,通過Thread的構造函數傳遞進去的Runnable的實現類。而線程啟動的時候,調用run方法,run方法又接著調用Runnable實現類的run方法!!!!
實現Callable接口
在JDK1.5之后又給我們提供了一種新的創建線程的方式:實現Callable方法。具體代碼如下:
FutureTask<Integer> ft = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int i = 0;
for(;i<10;i++){
System.out.println("線程創建的第三種方式:"+Thread.currentThread().getName());
}
return i;
}
});
new Thread(ft).start();
在上面的代碼中,在創建FutureTask對象的時候,我們把Callable的一個匿名實現類當做參數傳到了FutureTask的構造函數中,而在啟動線程的時候,我們又把創建的FutureTask的對象當做參數傳到了Thread的構造函數中。在這里請注意我們此時不是覆蓋的run方法,而是一個叫call的方法。大家可能會感到疑惑這個FutureTask和Callable這兩個到底是個什么玩意?下面我們一個一個的分析:
FutureTask
通過翻讀FutureTask的源碼我們可以看出來實現了RunnableFuture接口,而RunnableFuture接口又繼承了Runnable和Future接口。注意:這里是繼承了兩個接口!你可能會有疑問JAVA中不是沒有多繼承嗎?不錯,java中類是沒有多繼承的,而對于接口是有多繼承的!!!到這里我們明白一件事,那就是FutureTask是Runnable接口的一個實現類。到這里你是不是明白了點什么呢?
public class FutureTask<V> implements RunnableFuture<V>
public interface RunnableFuture<V> extends Runnable, Future<V>
如果你不明白的話,那就多看幾遍第二種創建線程的方式和它的原理吧。接下來我們來看一下FutureTask這個類中的run方法是怎么寫的:
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
上面這個方法中的代碼我沒有貼全,只貼出來了主要的部分。在這個方法中我們會發現這樣的兩句話Callable<V> c = callable;result = c.call();這兩句話就是關鍵!!!通過翻讀源碼我們就會發現源碼這個callable就是我們剛才傳到FutureTask中的Callable的實現類啊!
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
c.call()那不就是調用的Callable實現類的call方法嗎?!!!到這里終于真相大白了!FutureTask中其他方法有興趣的同學可以繼續研究一下。
Callable
在Callable這個接口中只有一個call方法。
總結
在實際編碼中,我們看到創建線程更多的是使用第二種方式,因為它更符合java中面向接口編程的思想。
最后出個題考一下大家,請說出下面代碼的運行結果:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Runnable實現類的調用:");
}
}){
@Override
public void run() {
System.out.println("繼承Thread的調用:");
}
}.start();