在併發編程中,最基本的就是建立線程了,那麼通常的建立姿式是怎樣的,又都有些什麼區別java
通常來說線程建立有四種方式:編程
因此本篇博文從佈局來說,分爲兩部分併發
目標: 建立兩個線程併發實現從1-1000的累加框架
實現邏輯以下ide
public class AddThread extends Thread { private int start, end; private int sum = 0; public AddThread(String name, int start, int end) { super(name); this.start = start; this.end = end; } public void run() { System.out.println("Thread-" + getName() + " 開始執行!"); for (int i = start; i <= end; i ++) { sum += i; } System.out.println("Thread-" + getName() + " 執行完畢! sum=" + sum); } public static void main(String[] args) throws InterruptedException { int start = 0, mid = 500, end = 1000; AddThread thread1 = new AddThread("線程1", start, mid); AddThread thread2 = new AddThread("線程2", mid + 1, end); thread1.start(); thread2.start(); // 確保兩個線程執行完畢 thread1.join(); thread2.join(); int sum = thread1.sum + thread2.sum; System.out.println("ans: " + sum); } }
輸出結果佈局
Thread-線程1 開始執行! Thread-線程2 開始執行! Thread-線程1 執行完畢! sum=125250 Thread-線程2 執行完畢! sum=375250 ans: 500500
通常實現步驟:學習
Thread
類run()
方法Thread#start()
執行邏輯比較清晰,只須要注意覆蓋的是run方法,而不是start方法this
public class AddRun implements Runnable { private int start, end; private int sum = 0; public AddRun(int start, int end) { this.start = start; this.end = end; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " 開始執行!"); for(int i = start; i <= end; i++) { sum += i; } System.out.println(Thread.currentThread().getName() + " 執行完畢! sum=" + sum); } public static void main(String[] args) throws InterruptedException { int start = 0, mid = 500, end = 1000; AddRun run1 = new AddRun(start, mid); AddRun run2 = new AddRun(mid + 1, end); Thread thread1 = new Thread(run1, "線程1"); Thread thread2 = new Thread(run2, "線程2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); int sum = run1.sum + run2.sum; System.out.println("ans: " + sum); } }
輸出結果線程
線程2 開始執行! 線程1 開始執行! 線程2 執行完畢! sum=375250 線程1 執行完畢! sum=125250 ans: 500500
通常實現步驟:code
Runnable
接口Thread#start()
啓動線程說明
相比於繼承Thread,這裏是實現一個接口,最終依然是藉助 Thread#start()
來啓動線程
而後就有個疑問:
二者是否有本質上的區別,在實際項目中如何抉擇?
Callable接口相比於Runnable接口而言,會有個返回值,那麼如何利用這個返回值呢?
demo以下
public class AddCall implements Callable<Integer> { private int start, end; public AddCall(int start, int end) { this.start = start; this.end = end; } @Override public Integer call() throws Exception { int sum = 0; System.out.println(Thread.currentThread().getName() + " 開始執行!"); for (int i = start; i <= end; i++) { sum += i; } System.out.println(Thread.currentThread().getName() + " 執行完畢! sum=" + sum); return sum; } public static void main(String[] args) throws ExecutionException, InterruptedException { int start = 0, mid = 500, end = 1000; FutureTask<Integer> future1 = new FutureTask<>(new AddCall(start, mid)); FutureTask<Integer> future2 = new FutureTask<>(new AddCall(mid + 1, end)); Thread thread1 = new Thread(future1, "線程1"); Thread thread2 = new Thread(future2, "線程2"); thread1.start(); thread2.start(); int sum1 = future1.get(); int sum2 = future2.get(); System.out.println("ans: " + (sum1 + sum2)); } }
輸出結果
線程2 開始執行! 線程1 開始執行! 線程2 執行完畢! sum=375250 線程1 執行完畢! sum=125250 ans: 500500
通常實現步驟:
Callable
接口Callable
的實現類爲參數,建立FutureTask
實例FutureTask
做爲Thread的參數,建立Thread實例Thread#start
啓動線程FutreTask#get()
阻塞獲取線程的返回值說明
Callable接口相比Runnable而言,會有結果返回,所以會由FutrueTask進行封裝,以期待獲取線程執行後的結果;
最終線程的啓動都是依賴Thread#start
demo以下,建立固定大小的線程池,提交Callable任務,利用Future獲取返回的值
public class AddPool implements Callable<Integer> { private int start, end; public AddPool(int start, int end) { this.start = start; this.end = end; } @Override public Integer call() throws Exception { int sum = 0; System.out.println(Thread.currentThread().getName() + " 開始執行!"); for (int i = start; i <= end; i++) { sum += i; } System.out.println(Thread.currentThread().getName() + " 執行完畢! sum=" + sum); return sum; } public static void main(String[] arg) throws ExecutionException, InterruptedException { int start=0, mid=500, end=1000; ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Integer> future1 = executorService.submit(new AddPool(start, mid)); Future<Integer> future2 = executorService.submit(new AddPool(mid+1, end)); int sum = future1.get() + future2.get(); System.out.println("sum: " + sum); } }
輸出
pool-1-thread-1 開始執行! pool-1-thread-2 開始執行! pool-1-thread-1 執行完畢! sum=125250 pool-1-thread-2 執行完畢! sum=375250 sum: 500500
通常實現邏輯:
Future#get
獲取返回的結果上面雖說是有四種方式,但實際而言,主要劃分爲兩類
此外,還有一種利用Fork/Join框架來實現併發的方式,後續專門說明,此處先略過
先把線程池的方式拎出來單獨說,這裏主要對比Thread, Callable, Runnable三中方式的區別
我的理解,線程的這兩種方式的區別也就只有繼承和實現接口的本質區別:
一個是繼承Thread類,能夠直接調用實例的 start()
方法來啓動線程;另外一個是實現接口,須要藉助 Thread#start()
來啓動線程
繼承由於java語言的限制,當你的任務須要繼承一個自定義的類時,會有缺陷;而實現接口卻沒有這個限制
至於網上不少地方說的實現Runnable接口更利於資源共享什麼的,好比下面這種做爲對比的
public class ShareTest { private static class MyRun implements Runnable { private volatile AtomicInteger ato = new AtomicInteger(5); @Override public void run() { while (true) { int tmp = ato.decrementAndGet(); System.out.println(Thread.currentThread() + " : " + tmp); if (tmp <= 0) { break; } } } } public static void main(String[] args) throws InterruptedException { MyRun run = new MyRun(); Thread thread1 = new Thread(run, "線程1"); Thread thread2 = new Thread(run, "線程2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("over"); } }
輸出:
Thread[線程1,5,main] : 4 Thread[線程2,5,main] : 3 Thread[線程1,5,main] : 2 Thread[線程2,5,main] : 1 Thread[線程1,5,main] : 0 Thread[線程2,5,main] : -1 over
MyRun
實現Runnable
接口,而後建立一個實例,將這個實例做爲多個Thread的參數構造Thread類,而後啓動線程,發現這幾個線程共享了 MyRun#ato
變量
然而上面這個實現接口改爲繼承Thread,其餘都不變,也沒啥兩樣
public class ShareTest { private static class MyRun extends Thread { private volatile AtomicInteger ato = new AtomicInteger(5); @Override public void run() { while (true) { int tmp = ato.decrementAndGet(); System.out.println(Thread.currentThread() + " : " + tmp); if (tmp <= 0) { break; } } } } public static void main(String[] args) throws InterruptedException { MyRun run = new MyRun(); Thread thread1 = new Thread(run, "線程1"); Thread thread2 = new Thread(run, "線程2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("over"); } }
輸出以下
Thread[線程1,5,main] : 4 Thread[線程2,5,main] : 3 Thread[線程1,5,main] : 2 Thread[線程1,5,main] : 0 Thread[線程2,5,main] : 1 Thread[線程2,5,main] : -1 over
上面除了說明使用Runnable更利於資源共享神馬的,其實並無以外,還有一個比較有意思的,爲何會輸出-1?
若是我這個任務是售票的話,妥妥的就超賣了,這個問題留待後續詳解
這兩個就比較明顯了,最根本的就是
從根源出發,就直接致使使用姿式上的區別
舉個形象的例子說明兩種方式的區別:
小明家今兒沒米了,小明要吃飯怎麼辦?
小明他媽對小明說,去你大爺家吃飯吧,至於小明到底吃沒吃着,小媽他媽就無論了,這就是Runnable方式;
小明他媽一想,這一家子都要吃飯,我先炒個菜,讓小明去大爺家借點米來,因此就等着小明拿米回來開鍋,這就是Callable方式
1.Runnable
Runnable不關心返回,因此任務本身默默的執行就能夠了,也不用告訴我完成沒有,我不care,您本身隨便玩,因此通常使用就是
new Thread(new Runnable() { public void run() {...} }).start()
換成JDK8的 lambda表達式就更簡單了 new Thread(() -> {}).start();
2.Callable
相比而言,callbale就悲催一點,無法這麼隨意了,由於要等待返回的結果,可是這個線程的狀態我又控制不了,怎麼辦?藉助FutrueTask
來玩,因此通常能夠看到使用方式以下:
FutureTask<Object> future = new FutureTask<>(() -> null); new Thread(future).start(); Object obj = future.get(); // 這裏會阻塞,直到線程返回值
這個就高端了,線程池一聽就感受厲害了,前面的四中方式有三種都是Thread#start()
來啓動線程,這也是咱們最經常使用的方式,這裏單獨說一下線程池的使用姿式
ExecutorService#submit()
提交線程Future<Object>
接收返回ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Integer> future1 = executorService.submit(()-> 10); int ans = future1.get();
說明,這裏提交線程以後,並不表示線程立馬就要執行,也不表示必定能夠執行(這個留待後續線程池的學習中探討)
繼承Thread類,覆蓋run方法,調用 Thread#start
啓動
實現Runnable接口,建立實例,做爲Thread
構造參數傳入,調用 Thread#start
啓動
new Thread(() -> {}).start()
實現Callable接口,建立實例,做爲FutureTask<>
構造參數建立FutureTask
對象,將FutureTask對象做爲Thread
構造參數傳入,調用 Thread#start
啓動
FutureTask<Object> future = new FutureTask<>(() -> null); new Thread(future).start(); Object obj = future.get(); // 這裏會阻塞,直到線程返回值
建立一個線程池,利用 ExecutorService#submit()
提交線程,Future<Object>
接收返回
ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Integer> future1 = executorService.submit(()-> 10); int ans = future1.get();
盡信書則不如,已上內容,純屬一家之言,因本人能力通常,見識有限,若有問題,請不吝指正,感激