1. 程式人生 > 其它 >Java多執行緒的幾種實現方法?說明它們的異同.

Java多執行緒的幾種實現方法?說明它們的異同.

說起java多執行緒程式設計,大家都不陌生,下面我就總結下java裡實現多執行緒的集中方法:繼承Thread類,實現Runnable介面,使用Callable和Future建立執行緒,使用執行緒池建立(使用java.util.concurrent.Executor介面)

1.繼承Thread類建立執行緒

通過繼承Thread類來建立並啟動多執行緒的一般步驟如下:

  1. 定義Tread類的子類MyThread,並重寫run()方法.run()方法的方法體(執行緒執行體)就是執行緒要執行的任務。
  2. 建立MyThread類的例項
  3. 呼叫子類例項的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())。

關於Java建立多執行緒的四種方式就分享到這裡了,感謝各位大佬觀看!