java多線程—Runnable、Thread、Callable區別

多線程編程優勢

  1. 進程之間不能共享內存,但線程之間共享內存很是容易。

  2. 系統建立線程所分配的資源相對建立進程而言,代價很是小。

Java中實現多線程有3種方法:

  • 繼承Thread類

  • 實現Runnable接口

  • 實現Callable接口(參考<Java編程思想(第4版)>  21.2.4章節,原來一直覺得是2種,後來發現是3種)

第一種實現方法—繼承Thread類

繼承Thread類,須要覆蓋方法 run()方法,在建立Thread類的子類時須要重寫 run(),加入線程所要執行的代便可。java

下邊是一個賣票程序小例子:編程

 1 package ThreadOne;
 2 
 3 public class ThreadByExtends {
 4 
 5     public static void main(String[] args) {
 6         // TODO Auto-generated method stub
 7         new MyThread().start();
 8         new MyThread().start();
 9         new MyThread().start();
10     }
11 
12 }
13 
14 class MyThread extends Thread {
15     private int ticket = 5;
16 
17     public void run() {
18 
19         for (int i = 0; i < 10; i++) {
20             if (ticket > 0) {
21                 System.out.println("車票第" + ticket-- + "張");
22             }
23         }
24     }
25 
26 }

輸出結果爲:安全

多線程1

這樣代碼的寫法簡單,符合你們的習慣,可是直接繼承Thread類有一個很大的缺點,由於「java類的繼承是單一的,extends後面只能指定一個父類」,全部若是當前類繼承Thread類以後就不能夠繼承其餘類。若是咱們的類已經從一個類繼承(如Swing繼承自 Panle 類、JFram類等),則沒法再繼承 Thread 類,這時若是咱們又不想創建一個新的類,應該怎麼辦呢?多線程

第二種實現方法—實現Runnable接口

若是要實現多繼承就得要用implements,Java 提供了接口 java.lang.Runnable 來解決上邊的問題。異步

Runnable是能夠共享數據的,多個Thread能夠同時加載一個Runnable,當各自Thread得到CPU時間片的時候開始運行RunnableRunnable裏面的資源是被共享的,因此使用Runnable更加的靈活。spa

下邊仍是賣票例子:.net

 1 package ThreadOne;
 2 
 3 public class ThreadRunnable {
 4 
 5     public static void main(String[] args) {
 6         MyThread1 myThread = new MyThread1();
 7         new Thread(myThread).start();
 8         new Thread(myThread).start();
 9     }
10 }
11 
12 class MyThread1 implements Runnable {
13 
14     private int ticket = 5;
15 
16     public void run() {
17         for (int i = 0; i < 10; i++) {
18             if (ticket > 0) {
19                 System.out.println("ticket = " + ticket--);
20             }
21         }
22     }
23 
24 }

 

輸出結果:線程

多線程3

  1. 在第二種方法(Runnable)中,ticket輸出的順序並非54321,這是由於線程執行的時機難以預測,ticket--並非原子操做(關於原子操做後邊會有詳解)。
  2. 在第一種方法中,咱們new了3個Thread對象,即三個線程分別執行三個對象中的代碼,所以即是三個線程去獨立地完成賣票的任務;而在第二種方法中,咱們一樣也new了3個Thread對象,但只有一個Runnable對象,3個Thread對象共享這個Runnable對象中的代碼,所以,便會出現3個線程共同完成賣票任務的結果。若是咱們new出3個Runnable對象,做爲參數分別傳入3個Thread對象中,那麼3個線程便會獨立執行各自Runnable對象中的代碼,即3個線程各自賣5張票。
  3. 在第二種方法中,因爲3個Thread對象共同執行一個Runnable對象中的代碼,所以可能會形成線程的不安全,好比可能ticket會輸出-1(若是咱們System.out....語句前加上線程休眠操做,該狀況將頗有可能出現),這種狀況的出現是因爲,一個線程在判斷ticket爲1>0後,尚未來得及減1,另外一個線程已經將ticket減1,變爲了0,那麼接下來以前的線程再將ticket減1,便獲得了-1。這就須要加入同步操做(即互斥鎖),確保同一時刻只有一個線程在執行每次for循環中的操做。而在第一種方法中,並不須要加入同步操做,由於每一個線程執行本身Thread對象中的代碼,不存在多個線程共同執行同一個方法的狀況。

第三種—實現Callable接口

Runnable是執行工做的獨立任務,可是它不返回任何值。若是你但願任務在完成的能返回一個值,那麼能夠實現Callable接口而不是Runnable接口。在Java SE5中引入的Callable是一種具備類型參數的泛型,它的參數類型表示的是從方法call()(不是run())中返回的值。code

例子以下:orm

 1 package ThreadOne;
 2 
 3 import java.awt.Panel;
 4 import java.util.concurrent.Callable;
 5 import java.util.concurrent.Future;
 6 import java.util.concurrent.FutureTask;
 7 
 8 public class ThreadCallable extends Panel {
 9 
10     public static void main(String[] args) {
11 
12         MyThread2 myThread2 = new MyThread2();
13 
14         FutureTask<Integer> futureTask = new FutureTask<>(myThread2);
15         new Thread(futureTask, "線程名:有返回值的線程2").start();
16 
17         try {
18             System.out.println("子線程的返回值:" + futureTask.get());
19         } catch (Exception e) {
20             e.printStackTrace();
21         }
22     }
23 }
24 
25 class MyThread2 implements Callable<Integer> {
26 
27     public Integer call() throws Exception {
28         System.out.println("當前線程名——" + Thread.currentThread().getName());
29         int i = 0;
30         for (; i < 5; i++) {
31             System.out.println("循環變量i的值:" + i);
32         }
33 
34         return i;
35     }
36 
37 }

 

運行結果以下:

多線程4

總結

實現Runnable接口相比繼承Thread類有以下優點:

  1. 能夠避免因爲Java的單繼承特性而帶來的侷限;
  2. 加強程序的健壯性,代碼可以被多個線程共享,代碼與數據是獨立的;
  3. 適合多個相同程序代碼的線程區處理同一資源的狀況。

 

實現Runnable接口和實現Callable接口的區別:

  1. Runnable是自從java1.1就有了,而Callable是1.5以後才加上去的
  2. Callable規定的方法是call(),Runnable規定的方法是run()
  3. Callable的任務執行後可返回值,而Runnable的任務是不能返回值(是void)
  4. call方法能夠拋出異常,run方法不能夠
  5. 運行Callable任務能夠拿到一個Future對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。經過Future對象能夠了解任務執行狀況,可取消任務的執行,還可獲取執行結果。
  6. 加入線程池運行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。

參考

http://blog.csdn.net/ns_code/article/details/17161237

http://tonl.iteye.com/blog/1874187

相關文章
相關標籤/搜索