多線程有幾種實現(xiàn)方式寨闹?如果被問到這個問題一定很頭疼,因為百度一下隨便就能出現(xiàn)各種各樣的答案君账。兩種繁堡、三種、四種乡数、五種椭蹄、六種、七種净赴。绳矩。。
但本質(zhì)上來講玖翅,個人認為只有一種方式:實現(xiàn)Runnable接口翼馆。
先放個圖:
1、實現(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();
...
}
}
實現(xiàn)Runnable接口金度,利用Runnable實例構(gòu)造Thread写妥,是較常用且最本質(zhì)實現(xiàn)。此構(gòu)造方法相當于對Runnable實例進行一層包裝审姓,在線程t
啟動時,調(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();
...
}
}
這種實現(xiàn)方式是顯示的繼承了Thread魔吐,但從類圖中我們可以看到,Thread類本身就繼承自Runnable莱找,所以繼承Thread的本質(zhì)依然是實現(xiàn)Runnable接口定義的run方法酬姆。
需要注意的是繼承Thread方式,target對象為null奥溺,重寫了run方法辞色,導致方式1中的Thread原生的run方法失效,因此并不會調(diào)用到target.run()的邏輯浮定,而是直接調(diào)用子類重寫的run方法相满。
因為java是單根繼承,此方式一般不常用桦卒。
3立美、實現(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);
}
}
實現(xiàn)Callable接口通過FutureTask包裝,可以獲取到線程的處理結(jié)果方灾,future.get()方法獲取返回值建蹄,如果線程還沒執(zhí)行完碌更,則會阻塞。
這個方法里洞慎,明明沒有看到run方法痛单,沒有看到Runnable,為什么說本質(zhì)也是實現(xiàn)Runnable接口呢劲腿?
回看開篇的類圖旭绒,F(xiàn)utureTask實現(xiàn)了RunnableFuture,RunnableFuture則實現(xiàn)了Runnable和Future兩個接口谆棱。因此構(gòu)造Thread時快压,F(xiàn)utureTask還是被轉(zhuǎn)型為Runnable使用。因此其本質(zhì)還是實現(xiàn)Runnable接口垃瞧。
至于FutureTask的工作原理蔫劣,后續(xù)篇章繼續(xù)分析。
4个从、匿名內(nèi)部類
匿名內(nèi)部類也有多種變體脉幢,上述三種方式都可以使用匿名內(nèi)部類來隱式實例化。
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)點在于使用方便嗦锐,不用額外定義類嫌松,缺點就是代碼可讀性差。
5奕污、Lambda表達式
Lambda表達式是jdk8引入的萎羔,已不是什么新東西,現(xiàn)在都jdk10了碳默。demo如下:
public class Demo{
public static void main(String[] args) throws Exception {
new Thread(() -> System.out.println("running") ).start() ;
...
}
}
如此簡潔的Lambda表達式贾陷,有沒有吸引到你呢?當然本質(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í)行線程的時候由ExecutorService去執(zhí)行该抒,最終還是利用Thread創(chuàng)建線程慌洪。線程池的優(yōu)勢在于線程的復用,從而提高效率凑保。
關(guān)于線程池冈爹,后續(xù)篇章會繼續(xù)詳解。
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("定時任務(wù)1執(zhí)行了....");
}
}), 2000, 1000);
}
}
TimerTask的實現(xiàn)了Runnable接口犯助,Timer內(nèi)部有個TimerThread繼承自Thread,因此繞回來還是Thread + Runnable维咸。
總結(jié)剂买,多線程的實現(xiàn)方式惠爽,在代碼中寫法千變?nèi)f化,但其本質(zhì)萬變不離其宗瞬哼。
多線程系列目錄(不斷更新中):
線程啟動原理
線程中斷機制
多線程實現(xiàn)方式
FutureTask實現(xiàn)原理
線程池之ThreadPoolExecutor概述
線程池之ThreadPoolExecutor使用
線程池之ThreadPoolExecutor狀態(tài)控制
線程池之ThreadPoolExecutor執(zhí)行原理
線程池之ScheduledThreadPoolExecutor概述
線程池的優(yōu)雅關(guān)閉實踐