Java多執行緒的幾種實現方法?說明它們的異同.
說起java多執行緒程式設計,大家都不陌生,下面我就總結下java裡實現多執行緒的集中方法:繼承Thread類,實現Runnable介面,使用Callable和Future建立執行緒,使用執行緒池建立(使用java.util.concurrent.Executor介面)
1.繼承Thread類建立執行緒
通過繼承Thread類來建立並啟動多執行緒的一般步驟如下:
- 定義Tread類的子類MyThread,並重寫run()方法.run()方法的方法體(執行緒執行體)就是執行緒要執行的任務。
- 建立MyThread類的例項
- 呼叫子類例項的start()方法來啟動執行緒
示例程式碼:
public class Mythread extends Thread { private int i; public void run(){//run()是執行緒類的核心方法 for(int i=0;i<10;i++){ System.out.println(this.getName()+":"+i); } } public static void main(String[] args) { Mythread t1=new Mythread(); Mythread t2=new Mythread(); Mythread t3=new Mythread(); t1.start(); t2.start(); t3.start(); } }
2.實現Runnable介面建立執行緒
通過實現Runnable介面建立並啟動執行緒的一般步驟如下:
1.定義Runnable介面的實現類,必須重寫run()方法,這個run()方法和Thread中的run()方法一樣,是執行緒的執行體
2.建立Runnable實現類的例項,並用這個例項作為Thread的target來建立Thread物件,這個Thread物件才是真正的執行緒物件
3.呼叫start()方法
示例程式碼:
public class MyThread implements Runnable{
@Override public void run() { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" : "+i); } } public static void main(String[] args) { MyThread myThread=new MyThread(); Thread thread1=new Thread(myThread,"執行緒1"); Thread thread2=new Thread(myThread,"執行緒2"); Thread thread3=new Thread(myThread,"執行緒3"); thread1.start(); thread2.start(); thread3.start(); }
}
3.覆寫Callable介面實現多執行緒
它雖然也是介面,但和Runnable介面不一樣,Callable介面提供了一個call()方法作為執行緒執行體,call()方法比run()方法功能要強大:
1.call方法可以有返回值
2.call()方法可以宣告丟擲異常
建立並啟動有返回值的執行緒的步驟如下:
1.建立Callable介面的實現類,並實現call()方法,然後建立該實現類的例項(從java8開始可以直接使用Lambda表示式建立Callable物件)。
2.使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了Callable物件的call()方法的返回值
3.使用FutureTask物件作為Thread物件的target建立並啟動執行緒(因為FutureTask實現了Runnable介面)
4.呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值
示例程式碼:
public class MyThread implements Callable<String>{//Callable是一個泛型介面
@Override
public String call() throws Exception {//返回的型別就是傳遞過來的V型別
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
}
return "Hello DengFengZi";
}
public static void main(String[] args) throws Exception {
MyThread myThread=new MyThread();
FutureTask<String> futureTask=new FutureTask<>(myThread);
Thread t1=new Thread(futureTask,"執行緒1");
Thread t2=new Thread(futureTask,"執行緒2");
Thread t3=new Thread(futureTask,"執行緒3");
t1.start();
t2.start();
t3.start();
System.out.println(futureTask.get());
}
}
FutureTask非常適合用於耗時的計算,主執行緒可以在完成自己的任務後,再去獲取結果。 FutureTask可以確保即使呼叫了多次run方法,它都只會執行一次Runnable或者Callable任務,或者通過cancel取消FutureTask的執行等。
疑問:
同時實現Runnable和Callable介面會怎麼樣?
結論:使用Runnable介面實現類做target建立的的執行緒和FutureTask的執行緒交叉著執行,但是由於FutureTask只執行一次Callable任務,在一次執行結束後就終止了程式。
4.使用執行緒池例如用Executor框架
什麼是Executor框架?
Executor框架包括:執行緒池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。
具體內容可以到我這篇部落格檢視
Executor執行Runnable任務
通過Executors的以上四個靜態工廠方法獲得 ExecutorService例項,而後呼叫該例項的execute(Runnable command)方法即可。一旦Runnable任務傳遞到execute()方法,該方法便會自動在一個執行緒上
示例程式碼:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCachedThreadPool{
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++){ //執行三個任務,那麼執行緒池中最多建立三個執行緒
executorService.execute(new TestRunnable());
System.out.println("************* a" + i + " *************");
}
executorService.shutdown();
}
}
class TestRunnable implements Runnable{
public void run(){
for(int i=0;i<5;i++){
System.out.println(Thread.currentThread().getName() + "執行緒被呼叫了。");
}
}
}
當通過實現Callable介面建立執行緒時,呼叫了Future介面的實現類FutureTask,該類還實現了Runnable介面,所以可以讓例項化物件作為Thread物件的Target。並且每次只執行一個執行緒的任務,當執行完執行緒的主任務後,返回值。
示例程式碼:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RunNewFixedThreadPool {
public static void main(String[] args) {
RunNewFixedThreadPool run = new RunNewFixedThreadPool();
ExecutorService executorService = Executors.newFixedThreadPool(6);
for (int i = 0; i < 5; i++) {//建立五個執行緒
executorService.execute(run.new MyRunnable(" "+(i+1)));
}
for (int i = 5; i < 10; i++) {//再建立五個執行緒,如果當前執行緒池中有執行緒可以使用,則複用,否則建立新的執行緒,但是執行緒池中最多隻能有6個執行緒
executorService.execute(run.new MyRunnable(" "+(i+1)));
}
}
public class MyRunnable implements Runnable{
private String username;
public MyRunnable(String username) {
this.username = username;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" username="+username+" begin "+System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" username="+username+" end "+System.currentTimeMillis());
}
}
}
這裡面每次建立的執行緒數可以設定,上面設為了5,所以一次性開啟了五個執行緒。如果設為3,那麼一次最多開三個程序,並且只有當一些程序關閉時,才會開啟相同數目的程序。
總結四種建立執行緒的方法
實現Runnable和實現Callable介面的方式基本相同,不過是後者執行call()方法有返回值,前者執行緒執行體run()方法無返回值,並且如果使用FutureTask類的話,只執行一次Callable任務。因此可以把這兩種方式歸為一種這種方式與繼承Thread類的方法之間的差別如下:
1、執行緒只是實現Runnable或實現Callable介面,還可以繼承其他類。
2、這種方式下,多個執行緒可以共享一個target物件,非常適合多執行緒處理同一份資源的情形。
3.繼承Thread類只需要this就能獲取當前執行緒。不需要使用Thread.currentThread()方法
4、繼承Thread類的執行緒類不能再繼承其他父類(Java單繼承決定)。
5、前三種的執行緒如果建立關閉頻繁會消耗系統資源影響效能,而使用執行緒池可以不用執行緒的時候放回執行緒池,用的時候再從執行緒池取,專案開發中主要使用執行緒池的方式建立多個執行緒。
6.實現介面的建立執行緒的方式必須實現方法(run() call())。