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


多線程編程優點

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

  2. 系統創建線程所分配的資源相對創建進程而言,代價非常小。

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

  • 繼承Thread類

  • 實現Runnable接口

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

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

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

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

 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更加的靈活。

下邊還是賣票例子:

 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())中返回的值。

例子如下:

 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM