多線程有幾種實(shí)現(xiàn)方式?如果被問到這個(gè)問題一定很頭疼,因?yàn)榘俣纫幌码S便就能出現(xiàn)各種各樣的答案。兩種、三種、四種、五種、六種、七種。。。
但本質(zhì)上來講,個(gè)人認(rèn)為只有一種方式:實(shí)現(xiàn)Runnable接口。
先放個(gè)圖:

1、實(shí)現(xiàn)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();
...
}
}
實(shí)現(xiàn)Runnable接口,利用Runnable實(shí)例構(gòu)造Thread,是較常用且最本質(zhì)實(shí)現(xiàn)。此構(gòu)造方法相當(dāng)于對(duì)Runnable實(shí)例進(jìn)行一層包裝,在線程t啟動(dòng)時(shí),調(diào)用Thread的run方法從而間接調(diào)用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();
...
}
}
這種實(shí)現(xiàn)方式是顯示的繼承了Thread,但從類圖中我們可以看到,Thread類本身就繼承自Runnable,所以繼承Thread的本質(zhì)依然是實(shí)現(xiàn)Runnable接口定義的run方法。
需要注意的是繼承Thread方式,target對(duì)象為null,重寫了run方法,導(dǎo)致方式1中的Thread原生的run方法失效,因此并不會(huì)調(diào)用到target.run()的邏輯,而是直接調(diào)用子類重寫的run方法。
因?yàn)閖ava是單根繼承,此方式一般不常用。
3、實(shí)現(xiàn)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(); //同步獲取返回結(jié)果
System.out.println(result);
}
}
實(shí)現(xiàn)Callable接口通過FutureTask包裝,可以獲取到線程的處理結(jié)果,future.get()方法獲取返回值,如果線程還沒執(zhí)行完,則會(huì)阻塞。
這個(gè)方法里,明明沒有看到run方法,沒有看到Runnable,為什么說本質(zhì)也是實(shí)現(xiàn)Runnable接口呢?
回看開篇的類圖,F(xiàn)utureTask實(shí)現(xiàn)了RunnableFuture,RunnableFuture則實(shí)現(xiàn)了Runnable和Future兩個(gè)接口。因此構(gòu)造Thread時(shí),F(xiàn)utureTask還是被轉(zhuǎn)型為Runnable使用。因此其本質(zhì)還是實(shí)現(xiàn)Runnable接口。
至于FutureTask的工作原理,后續(xù)篇章繼續(xù)分析。
4、匿名內(nèi)部類
匿名內(nèi)部類也有多種變體,上述三種方式都可以使用匿名內(nèi)部類來隱式實(shí)例化。
public class Demo{
public static void main(String[] args) throws Exception {
//方式一:Thread匿名內(nèi)部類
new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
}
}.start();
//方式二:Runnable匿名內(nèi)部類
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
}).start();
...
}
}
匿名內(nèi)部類的優(yōu)點(diǎn)在于使用方便,不用額外定義類,缺點(diǎn)就是代碼可讀性差。
5、Lambda表達(dá)式
Lambda表達(dá)式是jdk8引入的,已不是什么新東西,現(xiàn)在都jdk10了。demo如下:
public class Demo{
public static void main(String[] args) throws Exception {
new Thread(() -> System.out.println("running") ).start() ;
...
}
}
如此簡(jiǎn)潔的Lambda表達(dá)式,有沒有吸引到你呢?當(dāng)然本質(zhì)不多說,還是基于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);
...
}
}
線程池與前面所述其他方式的區(qū)別在于執(zhí)行線程的時(shí)候由ExecutorService去執(zhí)行,最終還是利用Thread創(chuàng)建線程。線程池的優(yōu)勢(shì)在于線程的復(fù)用,從而提高效率。
關(guān)于線程池,后續(xù)篇章會(huì)繼續(xù)詳解。
7、定時(shí)器
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("定時(shí)任務(wù)1執(zhí)行了....");
}
}), 2000, 1000);
}
}
TimerTask的實(shí)現(xiàn)了Runnable接口,Timer內(nèi)部有個(gè)TimerThread繼承自Thread,因此繞回來還是Thread + Runnable。
總結(jié),多線程的實(shí)現(xiàn)方式,在代碼中寫法千變?nèi)f化,但其本質(zhì)萬變不離其宗。
多線程系列目錄(不斷更新中):
線程啟動(dòng)原理
線程中斷機(jī)制
多線程實(shí)現(xiàn)方式
FutureTask實(shí)現(xiàn)原理
線程池之ThreadPoolExecutor概述
線程池之ThreadPoolExecutor使用
線程池之ThreadPoolExecutor狀態(tài)控制
線程池之ThreadPoolExecutor執(zhí)行原理
線程池之ScheduledThreadPoolExecutor概述
線程池的優(yōu)雅關(guān)閉實(shí)踐