Java 多線程 三種實現方式

Java多線程實現方式主要有三種:繼承Thread類、實現Runnable接 口、使用ExecutorService、Callable 實現有返回結果的多線程。其中前兩種方式線程執行完後都沒有返回值,只有最後一種Callable是帶返回值的,返回結果能夠從Future中取出來html

關於ExecutorService 參考:Java-線程池專題 (美團)java

一、繼承Thread類實現多線程
繼承Thread類的方法儘管被我列爲一種多線程實現方式,但Thread本質上也是實現了Runnable接口的一個實例,它表明一個線程的實例,而且,啓動線程的惟一方法就是經過Thread類的start()實例方法。start()方法是一個native方法,它將啓動一個新線程,並執行run()方法。這種方式實現多線程很簡單,經過本身的類直接extend Thread,並複寫run()方法,就能夠啓動新線程並執行本身定義的run()方法。例如:編程

public class MyThread extends Thread {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}  

 在合適的地方啓動線程以下:緩存

MyThread myThread1 = new MyThread();  
MyThread myThread2 = new MyThread();  
myThread1.start();  
myThread2.start();  

 二、實現Runnable接口方式實現多線程多線程

若是本身的類已經extends另外一個類,就沒法直接extends Thread,此時,必須實現一個Runnable接口,以下:併發

public class MyThread extends OtherClass implements Runnable {  
  public void run() {  
   System.out.println("MyThread.run()");  
  }  
}  

 爲了啓動MyThread,須要首先實例化一個Thread,並傳入本身的MyThread實例:框架

MyThread myThread = new MyThread();  
Thread thread = new Thread(myThread);  
thread.start();  

 事實上,當傳入一個Runnable target參數給Thread後,Thread的run()方法就會調用target.run(),參考JDK源代碼:異步

public void run() {  
  if (target != null) {  
   target.run();  
  }  
}  

三、使用ExecutorService、Callable 實現有返回結果的多線程ide

ExecutorService、Callable 這個對象實際上都是屬於Executor框架中的功能類。想要詳細瞭解Executor框架的能夠訪問 主題:java併發編程-Executor框架,這裏面對該框架作了很詳細的解釋。返回結果的線程是在JDK1.5中引入的新特徵,確實很實用,有了這種特徵我就不須要再爲了獲得返回值而大費周折了,並且即使實現了也可能漏洞百出。
可返回值的任務必須實現Callable接口,相似的,無返回值的任務必須Runnable接口。執行Callable任務後,能夠獲取一個Future的對象,在該對象上調用get就能夠獲取到Callable任務返回的Object了,再結合線程池接口ExecutorService就能夠實現傳說中有返回結果的多線程了。下面提供了一個完整的有返回結果的多線程測試例子,在JDK1.5下驗證過沒問題能夠直接使用。代碼以下:post

import java.util.concurrent.*;  
import java.util.Date;  
import java.util.List;  
import java.util.ArrayList;    
/** 
* 有返回值的線程 
*/  
@SuppressWarnings("unchecked")  
public class Test {  
public static void main(String[] args) throws ExecutionException,  
    InterruptedException {  
   System.out.println("----程序開始運行----");  
   Date date1 = new Date();  
  
   int taskSize = 5;  
   // 建立一個線程池  
   ExecutorService pool = Executors.newFixedThreadPool(taskSize);  
   // 建立多個有返回值的任務  
   List<Future> list = new ArrayList<Future>();  
   for (int i = 0; i < taskSize; i++) {  
    Callable c = new MyCallable(i + " ");  
    // 執行任務並獲取Future對象  
    Future f = pool.submit(c);  
    // System.out.println(">>>" + f.get().toString());  
    list.add(f);  
   }  
   // 關閉線程池  
   pool.shutdown();  
  
   // 獲取全部併發任務的運行結果  
   for (Future f : list) {  
    // 從Future對象上獲取任務的返回值,並輸出到控制檯  
    System.out.println(">>>" + f.get().toString());  
   }  
  
   Date date2 = new Date();  
   System.out.println("----程序結束運行----,程序運行時間【"  
     + (date2.getTime() - date1.getTime()) + "毫秒】");  
}  
}  
  
class MyCallable implements Callable<Object> {  
private String taskNum;  
  
MyCallable(String taskNum) {  
   this.taskNum = taskNum;  
}  
  
public Object call() throws Exception {  
   System.out.println(">>>" + taskNum + "任務啓動");  
   Date dateTmp1 = new Date();  
   Thread.sleep(1000);  
   Date dateTmp2 = new Date();  
   long time = dateTmp2.getTime() - dateTmp1.getTime();  
   System.out.println(">>>" + taskNum + "任務終止");  
   return taskNum + "任務返回運行結果,當前任務時間【" + time + "毫秒】";  
}  
}  

 代碼說明:

上述代碼中Executors類,提供了一系列工廠方法用於創先線程池,返回的線程池都實現了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads) 
建立固定數目線程的線程池。
public static ExecutorService newCachedThreadPool() 
建立一個可緩存的線程池,調用execute 將重用之前構造的線程(若是線程可用)。若是現有線程沒有可用的,則建立一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。
public static ExecutorService newSingleThreadExecutor() 
建立一個單線程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 
建立一個支持定時及週期性的任務執行的線程池,多數狀況下可用來替代Timer類。
ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。若是Executor後臺線程池尚未完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。  

具體 ExecutorService的使用 參考:Java-線程池專題 (美團)

2、Thread 和Runnable 的區別和聯繫

若是研究一下源碼就會發現:Thread其實自己就是實現了接口 Runnable的一個類;

所以 Thread中的方法和成員變量要比Runnable多,最典型地就是 Thread有start()方法,可是Runnable接口沒有start()方法;

若是想要執行一段線程: 

 public threadTest extends Thread {...
@Override
public void run() {..}
}

threadTest th1=new threadTest("一號窗口");     
th1.start();

這裏 繼承了Thread類,而且重寫了方法run();

而後調用 此類的start()方法來執行,記住不是調用run()方法執行 ,而是start(),由於:

 1.run()並非啓動線程,而是簡單的方法調用。

 2.並非一啓動線程(調用start()方法)就執行這個線程,而是進入就緒狀態,何時運行要看CPU。

 區別

實際開發中咱們一般採用Runnable接口來實現多線程。由於實現Runnable接口比繼承Thread類有以下好處: 
1. 避免繼承的侷限,一個類能夠繼承多個接口,可是類只能繼承一個類。 
2. Runnable接口實現的線程便於資源共享。而經過Thread類實現,各自線程的資源是獨立的,不方便共享。 

聯繫

public class Thread extends Object implements Runnable

 

發現Thread類也是Runnable接口的子類。

 

針對於區別的第二條:Runnable接口實現的線程便於資源共享,而經過Thread類實現,各自的線程的資源是獨立的,不方便共享。

舉個栗子:

網上最經典的賣票的例子:

(1)繼承Thread類: 

package com.thread;
/*
 * 經過繼承Thread類,實現多線程
 * Thread類是有run()方法的;
 * 也有start方法
 * */
public class threadTest extends Thread {
    private  int ticket =10;    
    private String name;    
    public threadTest(String name){
        this.name=name;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
//        super.run();
        while(true){
        if(this.ticket>0){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(this.name+"賣票--->"+(this.ticket--));
        }else{
            break;
        }
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        threadTest th1=new threadTest("一號窗口");     
        threadTest th2=new threadTest("二號窗口");      
        th1.start();
        th2.start();
//        threadTest mt=new threadTest();
//        Thread t1 =new Thread(mt,"一號窗口");
//        Thread t2 =new Thread(mt,"二號窗口");
//        t1.start();
//        t2.start();

    }

} 

 

 (2)實現Runnable接口: 

package com.thread;
/*
 * 經過實現Runnable接口,實現多線程
 * Runnable類是有run()方法的;
 * 可是沒有start方法
 * */

public class runnableTest implements Runnable {    
    private  int ticket =10; 
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            if(ticket>0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            System.out.println(Thread.currentThread().getName()+"賣票--->"+(this.ticket--));
            }else{
                break;
            }
        }
    }

    public static void main(String[] args) {        
        runnableTest mt=new runnableTest();
        Thread t1 =new Thread(mt,"一號窗口");
        Thread t2 =new Thread(mt,"二號窗口");
        t1.start();
        t2.start();
    }
}

能夠看到:Thread 各線程資源是獨立的,Runnable 便於實現線程共享資源; 

 

 

Runable Callable Future 的區別與聯繫

Runable Callable Future都是咱們在java多線程開發中遇到的接口,那麼這些接口之間有什麼區別呢?

Runable

做爲咱們多線程開發中常用到的接口,它定義run方法,只要對象實現這個方法,將對象做爲參數輸入到new Thread(Runnable A ),線程一旦start(),那麼就 自動執行了,沒有任何的返回結果,沒法知道何時結束,適用於徹底異步的任務,不用關心結果。樣例:

 

Callable

 

Callable定義的接口call(),它可以拋出異常,而且可以有一個返回結果。實現了Callable要想提交到線程池中, 直接經過executorService.submit(new CallAbleTask(i)),可是返回的結果是Future,結果信息從Future裏面取出,具體的業務邏輯在call中執行。好了下面介紹下Future

Future

 

Future提供了五個接口,功能以下圖:

總的來講Future,可以控制Callable對象的執行,檢測是否作完,能夠阻塞式獲取結果,也能夠等待一段時間內獲取結果,具體的方法含義由上圖可見:

boolean cancel(boolean mayInterruptIfRunning):用來取消任務,成功返回true,失敗則返回false

boolean isCancelled():表示任務是否被取消成功,若是在任務正常完成前被取消成功,則返回 true

boolean isDone():表示任務是否已經完成,若任務完成,則返回true

V get():用來獲取執行結果,這個方法會產生阻塞會一直等到任務執行完畢才返回

V get(long timeout, TimeUnit unit) 用來獲取執行結果,若是在指定時間內,還沒獲取到結果,直接返回null

。咱們看下例子

咱們可以看到方法的執行都是Callable,可是最後獲取結果經過Future,get的方式的話,就是一直會阻塞在那裏獲取。

以上總結:

Runable適用於徹底異步的任務,不用操心執行狀況,異常出錯的。

Callable適用於須要由返回結果的,對執行中的異常要知曉的,須要提交到線程池中。

Future主要是線程池執行Callable任務,返回的結果。它可以中斷任務的執行,一直等待結果,或者等待一段時間獲取結果。

參考:Runable Callable Future 的區別與聯繫

參考:Java中繼承thread類與實現Runnable接口的區別

參考:Runable和thread的區別(多線程必須用Runable) 

參考:JAVA多線程實現和應用總結

相關文章
相關標籤/搜索