多線程有幾種實現方式?如果被問到這個問題一定很頭疼,因為百度一下隨便就能出現各種各樣的答案。兩種、三種、四種、五種、六種、七種。。。
但本質上來講,個人認為只有一種方式:實現Runnable接口。
先放個圖:
1、實現Runnable接口
public class DemoThreadTask implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
}
public static void main(String[] args) {
DemoThreadTask task = new DemoThreadTask();
Thread t = new Thread(task);
t.start();
...
}
}
實現Runnable接口,利用Runnable實例構造Thread,是較常用且最本質實現。此構造方法相當于對Runnable實例進行一層包裝,在線程t
啟動時,調用Thread的run方法從而間接調用target.run():
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
public void run() {
if (target != null) {
target.run();
}
}
...
}
2、繼承Thread類
public class DemoThread extends Thread{
@Override
//重寫run方法
public void run() {
// TODO Auto-generated method stub
}
public static void main(String[] args) {
DemoThread t = new DemoThread();
t.start();
...
}
}
這種實現方式是顯示的繼承了Thread,但從類圖中我們可以看到,Thread類本身就繼承自Runnable,所以繼承Thread的本質依然是實現Runnable接口定義的run方法。
需要注意的是繼承Thread方式,target對象為null,重寫了run方法,導致方式1中的Thread原生的run方法失效,因此并不會調用到target.run()的邏輯,而是直接調用子類重寫的run方法。
因為java是單根繼承,此方式一般不常用。
3、實現Callable接口并通過FutureTask包裝
先看demo:
public class DemoCallable implements Callable<String>{
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return null;
}
public static void main(String[] args) throws Exception {
DemoCallable c = new DemoCallable();
FutureTask<String> future = new FutureTask<>(c);
Thread t = new Thread(future);
t.start();
...
String result = future.get(); //同步獲取返回結果
System.out.println(result);
}
}
實現Callable接口通過FutureTask包裝,可以獲取到線程的處理結果,future.get()方法獲取返回值,如果線程還沒執行完,則會阻塞。
這個方法里,明明沒有看到run方法,沒有看到Runnable,為什么說本質也是實現Runnable接口呢?
回看開篇的類圖,FutureTask實現了RunnableFuture,RunnableFuture則實現了Runnable和Future兩個接口。因此構造Thread時,FutureTask還是被轉型為Runnable使用。因此其本質還是實現Runnable接口。
至于FutureTask的工作原理,后續篇章繼續分析。
4、匿名內部類
匿名內部類也有多種變體,上述三種方式都可以使用匿名內部類來隱式實例化。
public class Demo{
public static void main(String[] args) throws Exception {
//方式一:Thread匿名內部類
new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
}
}.start();
//方式二:Runnable匿名內部類
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
}).start();
...
}
}
匿名內部類的優點在于使用方便,不用額外定義類,缺點就是代碼可讀性差。
5、Lambda表達式
Lambda表達式是jdk8引入的,已不是什么新東西,現在都jdk10了。demo如下:
public class Demo{
public static void main(String[] args) throws Exception {
new Thread(() -> System.out.println("running") ).start() ;
...
}
}
如此簡潔的Lambda表達式,有沒有吸引到你呢?當然本質不多說,還是基于Runnable接口。
6、線程池
public class DemoThreadTask implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("running");
}
public static void main(String[] args) {
DemoThreadTask task = new DemoThreadTask();
ExecutorService ex = Executors.newCachedThreadPool();
ex.execute(task);
...
}
}
線程池與前面所述其他方式的區別在于執行線程的時候由ExecutorService去執行,最終還是利用Thread創建線程。線程池的優勢在于線程的復用,從而提高效率。
關于線程池,后續篇章會繼續詳解。
7、定時器
public class DemoTimmerTask {
public static void main(String[] args) throws Exception {
Timer timer = new Timer();
timer.scheduleAtFixedRate((new TimerTask() {
@Override
public void run() {
System.out.println("定時任務1執行了....");
}
}), 2000, 1000);
}
}
TimerTask的實現了Runnable接口,Timer內部有個TimerThread繼承自Thread,因此繞回來還是Thread + Runnable。
總結,多線程的實現方式,在代碼中寫法千變萬化,但其本質萬變不離其宗。
多線程系列目錄(不斷更新中):
線程啟動原理
線程中斷機制
多線程實現方式
FutureTask實現原理
線程池之ThreadPoolExecutor概述
線程池之ThreadPoolExecutor使用
線程池之ThreadPoolExecutor狀態控制
線程池之ThreadPoolExecutor執行原理
線程池之ScheduledThreadPoolExecutor概述
線程池的優雅關閉實踐