線程的創建一共有四種方式:
- 繼承於
Thread
類,重寫run()方法; - 實現
Runable
接口,實現里面的run()方法; - 使用
FutureTask
實現有返回結果的線程 - 使用
ExecutorService
、Executors
線程池。
在詳細了解這四種方法之前,先來理解一下為什么線程要這樣創建:形象點來說,Thread是一個工人,run()方法里面的便是他的任務欄,這個任務欄默認是空的。當你想要這個線程做點什么時,你可以重寫Thread里面的run方法,重寫這個工人的任務欄;也可以通過runable、callable接口,從外部賦予這個工人任務。還可以將任務交給一堆工人,誰有空就誰就承擔這個任務(線程池)。
一、四種方式的詳細介紹
1、繼承於Thread類,重寫run()方法
Thread thread = new MyThread();
//線程啟動
thread.start();
MyThread 類
//繼承Thread
class MyThread extends Thread{
//重寫run方法
@Override
public void run() {
//任務內容....
System.out.println("當前線程是:"+Thread.currentThread().getName());
}
}
運行結果:
當前線程是:Thread-0
如果線程類使用的很少,那么可以使用匿名內部類,請看下面的例子:
Thread thread = new Thread(){
@Override
public void run() {
//任務內容....
System.out.println("當前線程是:"+Thread.currentThread().getName());
}
};
2、實現Runable接口,實現里面的run()方法:
第一種方法- -繼承Thread類的方法,一般情況下是不建議用的,因為java是單繼承結構,一旦繼承了Thread類,就無法繼承其他類了。所以建議使用 實現Runable接口 的方法;
Thread thread = new Thread(new MyTask());
//線程啟動
thread.start();
MyTask 類:
//實現Runnable接口
class MyTask implements Runnable{
//重寫run方法
public void run() {
//任務內容....
System.out.println("當前線程是:"+Thread.currentThread().getName());
}
}
同樣,如果這個任務類(MyTask )用的很少,也可以使用匿名內部類:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//任務內容....
System.out.println("當前線程是:"+Thread.currentThread().getName());
}
});
3、使用 FutureTask 實現有返回結果的線程
FutureTask
是一個可取消的異步計算任務,是一個獨立的類,實現了 Future、Runnable接口。FutureTask
的出現是為了彌補 Thread 的不足而設計的,可以讓程序員跟蹤、獲取任務的執行情況、計算結果 。
因為 FutureTask
實現了 Runnable
,所以 FutureTask
可以作為參數來創建一個新的線程來執行,也可以提交給 Executor
執行。FutureTask
一旦計算完成,就不能再重新開始或取消計算。
FutureTask的構造方法
可以接受 Runnable
,Callable
的子類實例。
//創建一個 FutureTask,一旦運行就執行給定的 Callable。
public FutureTask(Callable<V> callable);
//創建一個 FutureTask,一旦運行就執行給定的 Runnable,並安排成功完成時 get 返回給定的結果 。
public FutureTask(Runnable runnable, V result)
@ Example1 FutureTask 的簡單例子
public class Test {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Double> task = new FutureTask(new MyCallable());
//創建一個線程,異步計算結果
Thread thread = new Thread(task);
thread.start();
//主線程繼續工作
Thread.sleep(1000);
System.out.println("主線程等待計算結果...");
//當需要用到異步計算的結果時,阻塞獲取這個結果
Double d = task.get();
System.out.println("計算結果是:"+d);
//用同一個 FutureTask 再起一個線程
Thread thread2 = new Thread(task);
thread2.start();
}
}
class MyCallable implements Callable<Double>{
@Override
public Double call() {
double d = 0;
try {
System.out.println("異步計算開始.......");
d = Math.random()*10;
d += 1000;
Thread.sleep(2000);
System.out.println("異步計算結束.......");
} catch (InterruptedException e) {
e.printStackTrace();
}
return d;
}
}
運行結果:
異步計算開始.......
主線程等待計算結果...
異步計算結束.......
計算結果是:1002.7806590582911
四、使用線程池ExecutorSerice、Executors
前面三種方法,都是顯式地創建一個線程,可以直接控制線程,如線程的優先級、線程是否是守護線程,線程何時啟動等等。而第四種方法,則是創建一個線程池,池中可以有1個或多個線程,這些線程都是線程池去維護,控制程序員不需要關心這些細節,只需要將任務提交給線程池去處理便可,非常方便。
創建線程池的前提最好是你的任務量大,因為創建線程池的開銷比創建一個線程大得多。
創建線程池的方式
ExecutorService
是一個比較重要的接口,實現這個接口的子類有兩個 ThreadPoolExecutor (普通線程池)、ScheduleThreadPoolExecutor (定時任務的線程池)。你可以通過這兩個類來創建一個線程池,但要傳入各種參數,不太方便。
為了方便用戶,JDK中提供了工具類Executors
,提供了幾個創建常用的線程池的工廠方法。由於篇幅原因,不細說,可參考我的並發系列文章。
@ Example2 Executors 創建單線程的線程池
public class MyTest {
public static void main(String[] args) {
//創建一個只有一個線程的線程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//創建任務,並提交任務到線程池中
executorService.execute(new MyRunable("任務1"));
executorService.execute(new MyRunable("任務2"));
executorService.execute(new MyRunable("任務3"));
}
}
class MyRunable implements Runnable{
private String taskName;
public MyRunable(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println("線程池完成任務:"+taskName);
}
}
二、關於run()方法的思考
看看下面這種情況:線程類Thread 接收了外部任務,同時又用匿名內部類的方式重寫了內部的run()方法,這樣豈不是有兩個任務,那么究竟會執行那個任務呢?還是兩個任務一起執行呢?
Thread thread = new Thread(new MyTask()){
@Override
public void run() {//重寫Thread類的run方法
System.out.println("Thread 類的run方法");
}
};
//線程啟動
thread.start();
//實現Runnable接口
class MyTask implements Runnable{
//重寫run方法
@Override
public void run() {
//任務內容....
System.out.println("這是Runnable的run方法");
}
}
運行結果:
Thread 類的run方法
通過上面的結果,可以看出:線程最后執行的是Thread類內部的run()方法,這是為什么呢?我們先來分析一下JDK的Thread源碼:
private Runnable target;
public void run() {
if (target != null) {
target.run();
}
}
一切都清晰明了了,Thread類的run方法在沒有重寫的情況下,是判斷一下是否有Runnable 對象傳進來,如果有,那么就調用Runnable 對象里的run方法;否則,就什么都不干,線程結束。所以,針對上面的例子,一旦你繼承重寫了Thread類的run()方法,而你又想可以接收Runable類的對象,那么就要加上super.run(),執行沒有重寫時的run方法,改造的例子如下:
Thread thread = new Thread(new MyTask()){
@Override
public void run() {//重寫Thread類的run方法
//調用父類Thread的run方法,即沒有重寫時的run方法
super.run();
System.out.println("Thread 類的run方法");
}
};
運行結果:
這是Runnable的run方法
Thread 類的run方法